void BpDocument::composeFireCharacteristicsDiagram( void )
{
    // Surface Module must be active and using fuel model inputs
    PropertyDict *prop = m_eqTree->m_propDict;
    if ( ! prop->boolean( "surfaceModuleActive" )
      || ! prop->boolean( "surfaceCalcFireCharacteristicsDiagram" ) )
    {
        return;
    }

    // Graph fonts.
    QFont  textFont( property()->string( "graphTextFontFamily" ),
                     property()->integer( "graphTextFontSize" ) );
    QColor textColor( property()->color( "graphTextFontColor" ) );
    QPen   textPen( textColor );
    QFont  subTitleFont( property()->string( "graphSubtitleFontFamily" ),
                    property()->integer( "graphSubtitleFontSize" ) );
    QColor subTitleColor( property()->color( "graphSubtitleFontColor" ) );

    // Open the result file
    QString resultFile = m_eqTree->m_resultFile;
    FILE *fptr = 0;
    if ( ! ( fptr = fopen( resultFile.latin1(), "r" ) ) )
    // This code block should never be executed!
    {
        QString text("");
        translate( text, "BpDocument:FireCharacteristicsDiagram:NoLogOpen",
            resultFile );
        error( text );
        return;
    }
    // Allocate ros and hpua data arrays
    int rows  = tableRows();
    int cols  = tableCols();
    int cells = rows * cols;
    double *hpua = new double[ cells ];
    checkmem( __FILE__, __LINE__, hpua, "double hpua", cells );
    double *ros = new double[ cells ];
    checkmem( __FILE__, __LINE__, ros, "double ros", cells );
    // Set the variable names we're looking for
    const char* hpuaName = "vSurfaceFireHeatPerUnitArea";
    const char* rosName = "vSurfaceFireSpreadAtHead";
    if ( prop->boolean( "surfaceConfSpreadDirInput" ) )
    {
        rosName = "vSurfaceFireSpreadAtVector";
    }
    // Read and store the ros and hpua values
    char   buffer[1024], varName[128], varUnits[128];
    int    row, col, cell;
    double value;
    double rosMax = 0.0;
    double hpuaMax = 0.0;
    while ( fgets( buffer, sizeof(buffer), fptr ) )
    {
        if ( strncmp( buffer, "CELL", 4 ) == 0 )
        {
            if ( strstr( buffer, hpuaName ) )
            {
                sscanf( buffer, "CELL %d %d %s cont %lf %s",
                    &row, &col, varName, &value, varUnits );
                cell = ( col - 1 ) + ( cols * ( row - 1) );
                if ( ( hpua[ cell ] = value ) > hpuaMax )
                {
                    hpuaMax = value;
                }
            }
            else if ( strstr( buffer, rosName ) )
            {
                sscanf( buffer, "CELL %d %d %s cont %lf %s",
                    &row, &col, varName, &value, varUnits );
                cell = ( col - 1 ) + ( cols * ( row - 1) );
                if ( ( ros[ cell ] = value ) > rosMax )
                {
                    rosMax = value;
                }
            }
        }
    }
    fclose( fptr );

    // Get variable pointers
    EqVar *hpuaVar = m_eqTree->m_varDict->find( "vSurfaceFireHeatPerUnitArea" );
    EqVar *rosVar = m_eqTree->m_varDict->find( "vSurfaceFireSpreadAtHead" );
    EqVar *fliVar = m_eqTree->m_varDict->find( "vSurfaceFireLineIntAtHead" );
    EqVar *flVar = m_eqTree->m_varDict->find( "vSurfaceFireFlameLengAtHead" );

    // Conversion factor
    double flFactor, fliFactor, rosFactor, hpuaFactor, offset;
    appSiUnits()->conversionFactorOffset(
        flVar->m_nativeUnits, flVar->m_displayUnits, &flFactor, &offset );
    appSiUnits()->conversionFactorOffset(
        fliVar->m_nativeUnits, fliVar->m_displayUnits, &fliFactor, &offset );
    appSiUnits()->conversionFactorOffset(
        hpuaVar->m_nativeUnits, hpuaVar->m_displayUnits, &hpuaFactor, &offset );
    appSiUnits()->conversionFactorOffset(
        rosVar->m_nativeUnits, rosVar->m_displayUnits, &rosFactor, &offset );

    // Determine which of four different chart scales to use
    static const int Scales = 4;
    static double RosScale[Scales] = { 100., 200., 400., 800. };        // ft/min
    static double HpuaScale[Scales] = { 2000., 4000., 8000., 16000. };  // Btu/ft2
    double rosScale = 0.;       // Max y-axis ros
    double hpuaScale = 0.;      // Max x-axis hpua
    int scale;
    for ( scale=0; scale<Scales; scale++ )
    {
        if ( rosMax < ( rosScale = RosScale[ scale ] ) )
        {
            break;
        }
    }
    for ( scale=0; scale<Scales; scale++ )
    {
        if ( hpuaMax < ( hpuaScale = HpuaScale[scale] ) )
        {
            break;
        }
    }
    // Set axis maximums to appropriate predefined scale in display units
    rosMax  = rosFactor * rosScale;
    hpuaMax = hpuaFactor * hpuaScale;
    double ratio = rosMax / hpuaMax;

    // Create the graph
    Graph  graph;
    GraphLine *graphLine;
    GraphMarker *graphMarker;
    static const int Points = 100;
    double l_x[Points];
    double l_y[Points];

    // Draw the four standard hauling chart fli-fl levels
    static const int Lines = 4;
    static const double Fli[Lines] = { 100., 500., 1000., 2000. };  // Btu/ft/s
    static const double Fl[Lines] = { 4., 8., 11., 15. };           // ft

    // Create the hauling chart lines
    // Put Fireline Int label 65% of the way along the HPUA axis (display units)
    double  xPosFli = 0.65 * hpuaMax;
    // Put Flame Length label 85% of the way along the HPUA axis (display units)
    double  xPosFl  = 0.85 * hpuaMax;
    // Fireline Int and Flame Length label Y positions (display units)
    double  yPosFl[Lines], yPosFli[Lines];
    // Icon locations (in display units)
    double xIcon[Lines+1], yIcon[Lines+1];
    double diff, minDiff;
    QString label;
    QPen    redPen( "red", 1 );
    QColor  blackColor( "black" );
    int     alignCenter = Qt::AlignHCenter | Qt::AlignVCenter;
    // Fireline intensity - flame length curves
    int     line, point;
    for ( line = 0; line < Lines; line++ )
    {
        minDiff = 999999999.;
        for ( point = 0; point < Points; point++ )
        {
            // Hpua value in native units (Btu/ft2)
            l_x[point] = ( (point+1) * hpuaScale ) / (double) Points;
            // Ros value in native units (ft/min)
            l_y[point] = 60. * Fli[line] / l_x[point];
            // Convert to display units
            l_x[point] *= hpuaFactor;
            l_y[point] *= rosFactor;
            // Check for curve inflection point (for icon placement)
            if ( ( diff = fabs( l_y[point]/l_x[point] - ratio ) ) < minDiff )
            {
                minDiff = diff;
                xIcon[line+1] = l_x[point];
                yIcon[line+1] = l_y[point];
            }
        }
        // Create a graph line (with its own copy of the data).
        graphLine = graph.addGraphLine( Points, l_x, l_y, redPen );

        // Fireline intensity label
        label = QString( "%1" ).arg( ( Fli[line] * fliFactor ), 0, 'f', 0 );
        yPosFli[line] = rosFactor * ( 60. * Fli[line] / ( xPosFli / hpuaFactor ) );
        graph.addGraphMarker( xPosFli, yPosFli[line], label, textFont,
            blackColor, alignCenter );

        // Flame length label
        label = QString( "%1" ).arg( ( Fl[line] * flFactor ), 0, 'f', 0 );
        yPosFl[line] = rosFactor * ( 60. * Fli[line] / ( xPosFl / hpuaFactor ) );
        graph.addGraphMarker( xPosFl, yPosFl[line], label, textFont,
            blackColor, alignCenter );
    } // Next line

    // Fireline intensity label and units
    translate( label, "BpDocument:FireCharacteristicsDiagram:FLI" );
    graph.addGraphMarker( xPosFli, ( yPosFli[Lines-1] + 0.10 * rosMax ),
        label, textFont, blackColor, alignCenter );
    graph.addGraphMarker( xPosFli, ( yPosFli[Lines-1] + 0.05 * rosMax ),
        fliVar->m_displayUnits, textFont, blackColor, alignCenter );

    // Flame length label and units
    translate( label, "BpDocument:FireCharacteristicsDiagram:FL" );
    graph.addGraphMarker( xPosFl, ( yPosFl[Lines-1] + 0.10 * rosMax ),
        label, textFont, blackColor, alignCenter );
    graph.addGraphMarker( xPosFl, ( yPosFl[Lines-1] + 0.05 * rosMax ),
        flVar->m_displayUnits, textFont, blackColor, alignCenter );

    // Add icons
    QPixmap pixmap[Lines];
    pixmap[0] = QPixmap( fireman_xpm );
    pixmap[1] = QPixmap( dozer_xpm );
    pixmap[2] = QPixmap( torchtree_xpm );
    pixmap[3] = QPixmap( mtnfire_xpm );
    xIcon[0] = yIcon[0] = 0.0;
    for ( line=0; line<Lines; line++ )
    {
        graphMarker = graph.addGraphMarker(
            xIcon[line] + ( 0.5 * ( xIcon[line+1] - xIcon[line] ) ),
            yIcon[line] + ( 0.5 * ( yIcon[line+1] - yIcon[line] ) ),
            "", textFont, blackColor, alignCenter );
        graphMarker->setGraphMarkerPixmap( pixmap[line] );
    }

    // Finally, add a marker for each output result
    QColor  bluePen( "blue" );
    for ( cell=0; cell<cells; cell++ )
    {
        //fprintf( stderr, "%02d: %3.2f  %3.2f\n", i, hpua[i], ros[i] );
        graph.addGraphMarker( hpua[cell], ros[cell],
            QString( "%1" ).arg( cell + 1 ), textFont, bluePen, alignCenter );
    }
    // Compose the graph
    EqVar *zVar = 0;
    GraphAxleParms xParms( 0.0, hpuaMax, 11 );
    GraphAxleParms yParms( 0.0, rosMax, 11 );
    composeGraphBasics( &graph, true, hpuaVar, rosVar, zVar, Lines,
        &xParms, &yParms );

    // Create a separate page for this graph.
    translate( label, "BpDocument:FireCharacteristicsDiagram:Caption" );
    graph.setSubTitle( label, subTitleFont, subTitleColor );
    startNewPage( label, TocHaulChart );

    // This is how we save the graph and its composer.
    m_composer->graph( graph,
        m_pageSize->m_marginLeft
            + m_pageSize->m_bodyWd * property()->real( "graphXOffset" ),
        m_pageSize->m_marginTop
            + m_pageSize->m_bodyHt * property()->real( "graphYOffset" ),
        m_pageSize->m_bodyWd * property()->real( "graphScaleWidth" ),
        m_pageSize->m_bodyHt * property()->real( "graphScaleHeight" )
    );
    // Be polite and stop the composer.
    m_composer->end();
    delete[] ros;   ros  = 0;
    delete[] hpua;  hpua = 0;
    return;
}