//////////////////////////////////////
// START of File: CE2_PIE.CPP
//////////////////////////////////////

//===================================================================
//
// COPYRIGHT NOTICE:  This code is protected by Copyright!
//
// Copyright (c) 1994, 1995, 1996, 1997 by DPC Technology.
// All rights reserved. 
//
// See files "cf_copyr.h" and "cf_licen.h" for details of 
// copyright and license specifications, and contact information.
//
//===================================================================

//
// Include this source file's header file:
// and any additional neccessary include statements.
//
#include	<cf_api/bf_defs.h>
#include    <cf_api/ce2_pie.hpp> 

#include    <math.h>
            // for cartesian-to-polar calcs below...

class       bf_Color;
#include    <cf_api/b0_color.hpp>

class       cf_2DPoint;
class       cf_2DRect;
#include    <cf_api/c0_dimen.hpp>

// b0_dspla.hpp objects: bf_Pen and bf_Brush get used below,
// but are pulled in by the hpp file's #include statements

class       cf_XYZ;
class       cf_DataGroup;
#include    <cf_api/c1_dgrou.hpp>

#define     CF_PIE_P2C_CORRECTION       0.1
            //=========================================================
            // When we convert from Polar to Cartesian Coordinates,
            // there is a tendency for the converted points to lie
            // outside the *projected* boundaries of the pie's
            // elliptical border, which makes for small flaws in
            // the pie face rendering (which only *shows* in 3D
            // pie charts) -- so if we *shorten* the conversion
            // radii (i.e shave 'em down just a *bit*) this effect
            // will go away.  Note that we have to apply this
            // correction to both 2D *and* 3D pie charts, because
            // the 3D pies build on a foundation established
            // by the 2D pies...
            //
            // This #define'ed constant shows up in the updateEngine()
            // routines for *both* 2D and 3D pies below...
            //=========================================================

//--------------------------------
// Implement class cf_eng_Pie2D:
//--------------------------------
cf_eng_Pie2D::cf_eng_Pie2D()

        : cf_eng_Polar( CF_TYPE_PIE, CF_DISP_2D )
{
    // Init display attributes:
    dispFaceBack( FALSE );
    dispFaceBottom( FALSE );
    dispFaceLeft( FALSE );

    disp2D3DMode( CF_DISP_2D ); // Make it a 2D Chart!...

    bRenderRadialEdges = FALSE;
    bRenderCircumference = FALSE;
}


cf_eng_Pie2D
    ::~cf_eng_Pie2D()
        {}


void
cf_eng_Pie2D::applyDataUpdate
            ( cf_ChartModel *p_cf_cmodlDataSource )
{
    // We need to discard old pie slice tracking points
    // if our data has been changed.
    cf_ChartEngine::applyDataUpdate( p_cf_cmodlDataSource );
                    //Use the functionality of the BASE member method
                    //to reset any existing chart-geometry-objects.

    // We will iterate over the current available groups
    // and generate pie data points from the sum of the
    // data elements in the parent chart's data groups:
    cf_DataGroup    *p_cf_dgAccessGroup = NULL;

    int     iStartAt = p_cf_cmodlDataSource->dataGroupsStartAt();
    int     iEndAt = p_cf_cmodlDataSource->dataGroupsEndAt();

    double  dDataSum = 0.0;
    double  dSingleYValue = 0.0;

    // Calc the Absolute-Value Total of all the current Chart Data:
    for( i = iStartAt; i <= iEndAt; i++ )
    {
        p_cf_dgAccessGroup = p_cf_cmodlDataSource->activateGroupUserData( i );

        // Since Pie Charts show ABSOLUTE Data,
        dSingleYValue = p_cf_dgAccessGroup->dataYSumAbs();
                        //...IGNORE any NEGATIVE data.

        dDataSum += dSingleYValue;
    }   // Data Abs-Value Sum completed.

    // Calc current 3D Box geometry values for
    // Cartesian-to-Polar cordinate conversion:
    double  dCurrentRadius
                = p_cf_kEnginesKernel_->getXMax()
                                - CF_PIE_P2C_CORRECTION;
                                // #define'ed at top of this file

    // Generate Virtual Polar-Coordinate Points
    // for each of the Chart's Data Items:
    double  dPercentVal = 0.0;
    double  dTickVal = 0.0;

    double  dAngleVal = 0.0;
    double  dTickRadianVal = 0.0;

    double  dYValToXPieCoord = 0.0;
    double  dYValToZPieCoord = 0.0;

    cf_3DPoint  *p_cf_3dpNewPoint = NULL;
    bf_Color   bf_clrTemp;

    for( i = iStartAt; i <= iEndAt; i++ )
    {
        p_cf_dgAccessGroup = p_cf_cmodlDataSource->activateGroupUserData( i );

        // Since Pie Charts show ABSOLUTE Data,
        dSingleYValue = p_cf_dgAccessGroup->dataYSumAbs();
                        //...IGNORE any NEGATIVE data.

        // Calc percent vals for pie bounds and tick mark placement:

        // BUT -- Avoid numeric overflows caused by small divisors:
        // Was originally: "if ( dDataSum == 0.0 )"
        if ( fabs( dDataSum ) < CF_NUM_ZERO_IF_LT )
            dPercentVal = 0.0;  // avoid division-by-zero!
        else                    // and numeric overflows!
        {
            // Hold the old val for just a second!...
            dTickVal    = dPercentVal;
            // Bump up the percent total...
            #ifdef CF_ASSERT_DVALS
            assert( fabs( dDataSum ) > CF_NUM_ZERO_IF_LT );
            #endif
            dPercentVal += ( ( dSingleYValue * 100.0 ) / dDataSum );
            // ...and AVERAGE to get the tick placement point %age!
            dTickVal    = ( dTickVal + dPercentVal )    / 2.0;
        }

        //-----------------------------------------------
        // Convert these percent vals to chart coords:
        //-----------------------------------------------
        dAngleVal = ( dPercentVal * 2.0 * CF_PI ) / 100.0;
        dTickRadianVal = ( dTickVal * 2.0 * CF_PI ) / 100.0;

        dYValToXPieCoord = dCurrentRadius * cos( dAngleVal );

        dYValToZPieCoord = - (dCurrentRadius * sin( dAngleVal ));

        p_cf_3dpNewPoint = new cf_3DPoint(  dYValToXPieCoord,
                                            0,
                                            dYValToZPieCoord );
        p_cf_3dpNewPoint
            ->dispAttribsPointTo( p_cf_dgAccessGroup->dispAttribs() );
        
        //this->p_cf_3dgtmsPolar->markPieRim( dTickRadianVal );

        //
        // Uncomment the following lines for debugging:
        //
        /*
        p_cf_3dpNewPoint->set3DMarker( TRUE );
        bf_clrTemp = p_cf_cmodlDataSource->paletteColor( i );
        p_cf_3dpNewPoint->setColor( bf_clrTemp );
         */

        this->addFaceObject( p_cf_3dpNewPoint );
    }

    return;

}   // end of cf_eng_Pie2D::applyDataUpdate(.,..)



//---------------------------------------------
// Note how we use the base class to transform
// our pie's ellipse boundary tracking points:
//---------------------------------------------
void
cf_eng_Pie2D::applyGeomXform()
{
    cf_eng_Polar::applyGeomXform();
}

void
cf_eng_Pie2D::applyProjXform()
{
    cf_eng_Polar::applyProjXform();
}


void
cf_eng_Pie2D::config3DBoxETC()
{
    cf_eng_Polar::config3DBoxETC();

    // Now -- If we have pie slice points,..
    if ( iNumFaceObjects_  > 1 )
    {
        // ... we will need to correct them to conform to the
        // 3D box -- we want them to lie flat on the floor:
        cf_3DPoint  *p_cf_3dpRetrievePoint
                    = (cf_3DPoint*) da_p_cf_objFaceObjects_[ 1 ];

        // The first pie slice point will serve as our
        // sample to get a y-translation value. Note
        // that we assume that all the objects in this
        // engine's face object pointer array are
        // cf_3DPoints.  Fortunately, this IS the case.
        double dCurrentYCoord = p_cf_3dpRetrievePoint->virtY();
        double dNewYCoord = p_cf_kEnginesKernel_->getYMin();
        double dXlateYCoordBy = dNewYCoord - dCurrentYCoord;

        for ( i = 1; i <= iNumFaceObjects_; i++ )
        {
            p_cf_3dpRetrievePoint = (cf_3DPoint*) da_p_cf_objFaceObjects_[ i ];
            p_cf_3dpRetrievePoint->applyTranslateOp( 0, dXlateYCoordBy, 0 );
        }

    }   // end of screen for existence of face points

}   // end of cf_eng_Pie2D::config3DBoxETC()



void
cf_eng_Pie2D::draw2D
            ( bf_Display *p_bf_dspFromChartView )
{
    cf_eng_Pie2D::draw3D( p_bf_dspFromChartView );
}



void
    cf_eng_Pie2D
        ::draw3D( bf_Display *p_bf_dspFromChartView )
{
    // Render our 3D Box with an "empty" pie-circle:
    cf_eng_Polar::draw3D( p_bf_dspFromChartView );

    // Use the Virtual Projection Points to determine the boundaries of
    // a rectangle that will define the XZ projected ellipse on the XY
    // Coordinate space of the current bf_Display to determine the bounding
    // rectangle for a projected ellipse that will mark the pie's boundary.
    cf_2DPoint cf_2dpProjXMin
                = this->cf_3dpXZPlaneEllipseProjXMin.screenPoint();
    cf_2DPoint cf_2dpProjXMax
                = this->cf_3dpXZPlaneEllipseProjXMax.screenPoint();
    cf_2DPoint cf_2dpProjZMin
                = this->cf_3dpXZPlaneEllipseProjZMin.screenPoint();
    cf_2DPoint cf_2dpProjZMax
                = this->cf_3dpXZPlaneEllipseProjZMax.screenPoint();

    cf_2DPoint cf_2dpVirtXMax
                = this->cf_3dpXZPlaneEllipseVirtXMax.screenPoint();
    cf_2DPoint cf_2dpVirtZMax
                = this->cf_3dpXZPlaneEllipseVirtZMax.screenPoint();

    // Test and update these default Min/Max Projection boundaries:
    cf_2DPoint cf_2dpUpperLeft = cf_2dpProjXMin;
    cf_2dpUpperLeft = cf_2dpUpperLeft.yieldMin( cf_2dpProjXMax );
    cf_2dpUpperLeft = cf_2dpUpperLeft.yieldMin( cf_2dpProjZMin );
    cf_2dpUpperLeft = cf_2dpUpperLeft.yieldMin( cf_2dpProjZMax );

    cf_2DPoint cf_2dpLowerRight = cf_2dpProjXMin;
    cf_2dpLowerRight = cf_2dpLowerRight.yieldMax( cf_2dpProjXMax );
    cf_2dpLowerRight = cf_2dpLowerRight.yieldMax( cf_2dpProjZMin );
    cf_2dpLowerRight = cf_2dpLowerRight.yieldMax( cf_2dpProjZMax );

    cf_2dpLowerRight = cf_2dpLowerRight.yieldMax( cf_2dpVirtXMax );
    cf_2dpLowerRight = cf_2dpLowerRight.yieldMax( cf_2dpVirtZMax );

    cf_2DRect  cf_2drBoundingEllipseRect( cf_2dpUpperLeft,
                                                cf_2dpLowerRight );

    // Process the Face Object List to generate 2D "Pie Slices":
    if ( iNumFaceObjects_ <= 0 )
    {   // We've got no data to render!

        // So draw an "empty" ellipse:
        p_bf_dspFromChartView->dsp_pushPen( CF_BLACK );
        p_bf_dspFromChartView->dsp_pushBrush( CF_WHITE );

        p_bf_dspFromChartView->dsp_ellipse(    cf_2dpUpperLeft,
                                                cf_2dpLowerRight );

        delete  p_bf_dspFromChartView->dsp_popBrush();
        delete  p_bf_dspFromChartView->dsp_popPen();
                // which deletes the resouces pushed above.
    }
    else if ( iNumFaceObjects_ == 1 )
    {   // Group one consumes ALL of the pie!
        // So draw an ellipse with the color of group one:

        cf_3DPoint  *p_cf_3dpRetrievePoint
                    = (cf_3DPoint*) da_p_cf_objFaceObjects_[ 1 ];

        cf_DispAttribs  *p_cf_datbOnlyGroup
                        = p_cf_3dpRetrievePoint->dispAttribsCurrent();
                        // We can now push/pop pens, brushes, etc...

        // Render the ellipse:
        p_cf_datbOnlyGroup->pushPenAttribs( p_bf_dspFromChartView );
        p_cf_datbOnlyGroup->pushBrushAttribs( p_bf_dspFromChartView );
        p_bf_dspFromChartView->dsp_ellipse( cf_2dpUpperLeft,
                                            cf_2dpLowerRight );
        p_cf_datbOnlyGroup->popBrushAttribs( p_bf_dspFromChartView );
        p_cf_datbOnlyGroup->popPenAttribs( p_bf_dspFromChartView );
            // ...and delete the resouce allocations..
    }
    else    // i.e. if ( iNumFaceObjects_ > 1 )
    {
//        cf_DataGroup    *p_cf_dgInSequence
//                        = p_cf_cmodlDataSource->activateGroupUserData( 1 );
//        double  dYSum = p_cf_dgInSequence->dataYSumAbs();

//        // Should we render this first (and maybe only) pie slice?
//        if ( dYSum > 0.0 )
//        {
            cf_eng_Pie2D::drawSlice(    cf_2drBoundingEllipseRect,
                                        iNumFaceObjects_,
                                        1,
                                        p_bf_dspFromChartView );
//        }

        // Render any more pie slices as we did the first --
        // from the array of xformed virtual 3D data points:
        for ( i = 1; i < iNumFaceObjects_; i++ )
        {
            // Look at each potential pie-slice...
//            p_cf_dgInSequence
//                = p_cf_cmodlDataSource->activateGroupUserData( i + 1 );
//            dYSum = p_cf_dgInSequence->dataYSumAbs();
            // but DON'T render "empty" slices:
//            if ( dYSum > 0.0 )
                cf_eng_Pie2D::drawSlice( cf_2drBoundingEllipseRect,
                                         i,
                                         i + 1,
                                         p_bf_dspFromChartView );
        }
    }   // End of rendering the whole pie, or the pie slices.

    // For debugging, you may want to uncomment these lines below...
    /*
    this->cf_3dpXZPlaneEllipseProjXMax
                    .draw3D( p_bf_dspFromChartView );
    this->cf_3dpXZPlaneEllipseProjXMin
                    .draw3D( p_bf_dspFromChartView );
    for ( i = 1; i <= iNumFaceObjects; i++ )
            da_p_cf_objFaceObjects_[ i ]
                    ->draw3D( p_bf_dspFromChartView );
    */

    // Process the Chart Marker List ( ribbons, etc):
    for ( i = 1; i <= iNumMarkerObjects_; i++ )
        ( (cf_Virt3DObj*) da_p_cf_objMarkerObjects_[ i ] )
                                    ->draw3D( p_bf_dspFromChartView );
    return;

}   // end of cf_eng_Pie2D::draw3D( ... )




void
    cf_eng_Pie2D
        ::drawSlice(    cf_2DRect   cf_2drEllipseRectangle,
                        int         iStartAtIndex,
                        int         iFinishAtIndex,
                        bf_Display  *p_bf_dspFromChartView )
{
    // To define critical screen points, we need to retrieve
    // the neccessary virtual (and transformed) 3D "data" points,
    // and get their projected screen coords:
    cf_2DPoint  cf_2dpStartOfArc;   // (re)set according to
    cf_2DPoint  cf_2dpEndOfArc;     // retrieved point(s) below...
    cf_3DPoint  *p_cf_3dpRetrievePoint = NULL;

    cf_2DPoint  a_cf_2dpRadialEdge[ 2 ];    // To render slice radii...
    a_cf_2dpRadialEdge[ 0 ] = this->cf_3dpXZPlaneEllipseCenter.screenPoint();
                            // pretty much "constant" within this scope...

    // Set the arc-start-point:
    p_cf_3dpRetrievePoint
        = (cf_3DPoint*) da_p_cf_objFaceObjects_[ iStartAtIndex ];
    cf_2dpStartOfArc = p_cf_3dpRetrievePoint->screenPoint();

    // Set the arc end-point:
    p_cf_3dpRetrievePoint
        = (cf_3DPoint*) da_p_cf_objFaceObjects_[ iFinishAtIndex ];
    cf_2dpEndOfArc = p_cf_3dpRetrievePoint->screenPoint();

    // Retrieve the display attribs (colors, etc) for this "slice":
    cf_DispAttribs  *p_cf_datbCurrSlice
                    = p_cf_3dpRetrievePoint->dispAttribsCurrent();
                    // We can now push/pop pens, brushes, etc...

    // Render the pie slice:
    p_cf_datbCurrSlice->pushPenAttribs( p_bf_dspFromChartView );
    p_cf_datbCurrSlice->pushBrushAttribs( p_bf_dspFromChartView );

    p_bf_dspFromChartView->dsp_pie( cf_2drEllipseRectangle,
                                    cf_2dpStartOfArc, cf_2dpEndOfArc );

    // ...and delete the resouce allocations pushed immed above.
    p_cf_datbCurrSlice->popBrushAttribs( p_bf_dspFromChartView );
    p_cf_datbCurrSlice->popPenAttribs( p_bf_dspFromChartView );

    // SOME platforms (notably OS/2) do not render pie slice EDGES
    // well -- so a final curcumference arc render-op REALLY helps!
    p_bf_dspFromChartView->dsp_pushPen( CF_BLACK );
    if ( this->bRenderCircumference == TRUE )
        p_bf_dspFromChartView->dsp_arc( cf_2drEllipseRectangle,
                                        cf_2dpStartOfArc, cf_2dpEndOfArc );

    // ...and re-rendering the radial edges really helps too!
    if ( this->bRenderRadialEdges == TRUE )
    {
        a_cf_2dpRadialEdge[ 1 ] = cf_2dpStartOfArc;
        p_bf_dspFromChartView->dsp_polyline( a_cf_2dpRadialEdge, 2 );
        a_cf_2dpRadialEdge[ 1 ] = cf_2dpEndOfArc;
        p_bf_dspFromChartView->dsp_polyline( a_cf_2dpRadialEdge, 2 );
    }

    delete  p_bf_dspFromChartView->dsp_popPen();
                // which deletes the resouces pushed above.

    return;

}   // end of cf_eng_Pie2D::drawSlice( ... )


void
    cf_eng_Pie2D
        ::renderAttribs(  BOOL  bReRenderRadialEdges,
                          BOOL  bReRenderCircumference )
{
    this->bRenderRadialEdges = bReRenderRadialEdges;
    this->bRenderCircumference = bReRenderCircumference;
    return;
}



//
// END of class cf_eng_Pie2D Implementation
//
    
//////////////////////////////////////
// END of File: CE2_PIE.CPP
//////////////////////////////////////

