<?PHP
#
#   FILE:  Graph.php
#
#   Part of the Collection Workflow Integration System (CWIS)
#   Copyright 2002-2014 Edward Almasy and Internet Scout Research Group
#   http://scout.wisc.edu/cwis/
#

class Graph {
    const TYPE_DATE = 1;
    const TYPE_DATE_BAR = 2;

    const OK = 1;
    const NO_DATA = 2;
    const ERROR_UNKNOWN_TYPE = 3;

    const DAILY = 0;
    const WEEKLY = 1;
    const MONTHLY = 2;

    /**
    * Object constructor, used to create a new Graph.
    * @param $GraphType Graph::TYPE_* giving the graph type.
    * @param $GraphData Array giving the data to be plotted.  Keys
    *  give x-coordinates, and values give an array of y-coordinates.
    */
    function __construct($GraphType, array $GraphData)
    {
        if (count($GraphData)==0)
        {
            $this->Status = self::NO_DATA;
        }
        elseif ($GraphType == self::TYPE_DATE ||
            $GraphType == self::TYPE_DATE_BAR )
        {
            $this->Status = self::OK;
            # Convert input data into a form that Javascript will deal with

            if ($GraphType == self::TYPE_DATE)
            {
                $this->Data = $this->ToJsFormat($GraphData);
            }
            elseif ($GraphType == self::TYPE_DATE_BAR)
            {
                # Summarize the search data passed in into daily, weekly, monthly:
                $DailyData = array();
                $WeeklyData = array();
                $MonthlyData = array();

                foreach ($GraphData as $Xval => $Yvals)
                {
                    $DailyTS = strtotime( date('Y-m-d', $Xval) );
                    # Find the Monday preceeding this day for the weekly TS:
                    $WeeklyTS = $DailyTS - 86400* (date('N',$Xval) - 1);
                    $MonthlyTS = strtotime( date('Y-m-01', $Xval) );

                    $this->AddToArray( $DailyData, $DailyTS, $Yvals );
                    $this->AddToArray( $WeeklyData, $WeeklyTS, $Yvals );
                    $this->AddToArray( $MonthlyData, $MonthlyTS , $Yvals);
                }

                $this->Data = array(
                    "Daily"  => $this->ToJsFormat( $DailyData ),
                    "Weekly" => $this->ToJsFormat( $WeeklyData ),
                    "Monthly"=> $this->ToJsFormat( $MonthlyData ) );
            }

            $this->Type = $GraphType;

            $this->Width = 960;
            $this->Height = 500;

            $this->LabelChars = strlen(max( array_map('max', $GraphData) ) ) ;

            $this->TopMargin = 5;
            $this->LeftMargin = 5;
            $this->RightMargin = 50;
            $this->BottomMargin = 40;

            $this->LabelFontSize = 14;

            $this->Legend = array();

            $this->Scale = self::DAILY;
            $this->Title = "";
        }
        else
        {
            $this->Status = self::ERROR_UNKNOWN_TYPE;
        }
    }

    /**
    * Determine if graph creation succeeded.
    * @return Graph::OK on success, other on failure.
    */
    function Status(){ return $this->Status; }

    /**
    * Set X axis label.
    * @param $Label label value.
    */
    function XLabel($Label){ $this->XLabel = $Label; }

    /**
    * Set Y axis label.
    * @param $Label label value.
    */
    function YLabel($Label){ $this->YLabel = $Label; }

    /**
    * Set label font size.
    * @param $Size in points.
    */
    function LabelFontSize($X){ $this->LabelFontSize = $X; }

    /**
    * Set top margin.
    * @param $Margin in pixels.
    */
    function TopMargin($X)    { $this->TopMargin = $X; }

    /**
    * Set right side margin.
    * @param $Margin in pixels.
    */
    function RightMargin($X)  { $this->RightMargin = $X; }

    /**
    * Set bottom margin.
    * @param $Margin in pixels.
    */
    function BottomMargin($X) { $this->BottomMargin = $X; }

    /**
    * Set left margin.
    * @param $Margin in pixels.
    */
    function LeftMargin($X)   { $this->LeftMargin = $X; }

    /**
    * Set graph width.
    * @param $Width in pixels.
    */
    function Width($X) { $this->Width = $X; }

    /**
    * Set graph height.
    * @param $Height in pixels.
    */
    function Height($X) { $this->Height = $X; }

    /**
    * Set graph legend.
    * @param array $SeriesNames
    */
    function Legend($X) { $this->Legend = $X; }

    /**
    * Determine default granularity for bar charts.
    * @param Scale one of Graph::DAILY, Graph::WEEKLY, or Graph::MONTHLY
    */
    function Scale($X) { $this->Scale = $X; }

    /**
    * Set graph title.
    * @param HTML fragment giving the title element (e.g., <h3>Shiny graph</h3).
    */
    function Title($X) { $this->Title = $X; }

    /**
    * Generate HTML/CSS/Javascript to display a graph.
    */
    function Display()
    {
        if ($this->Status == self::NO_DATA)
        {
            print $this->Title;
            print "<p><em>No data to display</em></p>";
            return;
        }

        if ($this->Status != self::OK)
            return;

        $ChartNumber = self::GetNumber();

        global $AF;
        ?>

        <?PHP if($ChartNumber==0) { ?>
        <script type="text/javascript" src="<?PHP $AF->PUIFile("d3.js"); ?>"></script>
        <script type="text/javascript" src="<?PHP $AF->PUIFile("CW-Graph.js"); ?>"></script>
        <style>
        .cw-chart { width: 100%; }
            .cw-chart-button { padding: 5px; margin-bottom: 15px; cursor: pointer; }
        svg { font: 12px sans-serif;}
        .axis { shape-rendering: crispEdges; }

        .axis path, .axis line {
            fill: none;
            stroke-width: .5px;
        }

        .x.axis path { stroke: #000; }
        .x.axis line { stroke: #fff; stroke-opacity: .5; }
        .y.axis line { stroke: #ddd; }

        path.line {
            fill: none;
            stroke-width: 1.5px;
        }

        rect.pane {
            cursor: move;
            fill: none;
            pointer-events: all;
        }

        .focus circle {
          fill: none;
          stroke: steelblue;
        }

        .focus { fill: black; }

        .line.graph_color0 { stroke: #4D7588; }
        .line.graph_color1 { stroke: ##975078; }

        .area.graph_color0 { fill: #4D7588; }
        .area.graph_color1 { fill: #975078; }
        .area.graph_color2 { fill: #818181; }
        .area.graph_color3 { fill: #5CAA5C; }
        .area.graph_color4 { fill: #DDBC6D; }

        </style>
        <?PHP } ?>

        <style>
          <?PHP if ($this->Type == self::TYPE_DATE) { ?>
          .x-label<?PHP print($ChartNumber); ?> { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }
          .y-label<?PHP print($ChartNumber); ?> { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }
          <?PHP } elseif ($this->Type == self::TYPE_DATE_BAR) { ?>
          .x-label<?PHP print($ChartNumber); ?>a { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }
          .y-label<?PHP print($ChartNumber); ?>a { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }

          .x-label<?PHP print($ChartNumber); ?>b { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }
          .y-label<?PHP print($ChartNumber); ?>b { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }

          .x-label<?PHP print($ChartNumber); ?>c { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }
          .y-label<?PHP print($ChartNumber); ?>c { font-weight: bold; font-size: <?PHP print $this->LabelFontSize; ?>px; }
          <?PHP } ?>
        </style>

        <?PHP if ($this->Type == self::TYPE_DATE) { ?>
          <?PHP print $this->Title; ?>
          <div class="cw-chart" id="chart<?PHP print $ChartNumber; ?>"></div>
        <?PHP } elseif ($this->Type == self::TYPE_DATE_BAR) { ?>
          <div style="max-width: <?PHP print $this->Width; ?>px;">

          <div>
            <span style="float: right; margin-top: 3px; padding-right: <?PHP print $this->LabelChars + 1 ; ?>em; margin-right: <?PHP print $this->RightMargin; ?>px; ">
                <span class="cw-chart-button" id="cw-chart-button<?PHP print $ChartNumber; ?>a"
                  <?PHP if ($this->Scale == self::DAILY) { ?> style="font-weight: bold;" <?PHP } ?>
                        >Daily</span>|
                <span class="cw-chart-button" id="cw-chart-button<?PHP print $ChartNumber; ?>b"
                  <?PHP if ($this->Scale == self::WEEKLY) { ?> style="font-weight: bold;" <?PHP } ?>
                        >Weekly</span>|
                <span class="cw-chart-button" id="cw-chart-button<?PHP print $ChartNumber; ?>c"
                   <?PHP if ($this->Scale == self::MONTHLY) { ?> style="font-weight: bold;" <?PHP } ?>
                        >Monthly</span>
            </span>
            <?PHP print $this->Title; ?>
          </div>

          <div id="chart<?PHP print $ChartNumber; ?>">
            <div class="cw-chart" id="chart<?PHP print $ChartNumber; ?>a"
              <?PHP if ($this->Scale != self::DAILY) { ?> style="display: none;" <?PHP } ?> ></div>
            <div class="cw-chart" id="chart<?PHP print $ChartNumber; ?>b"
              <?PHP if ($this->Scale != self::WEEKLY) { ?> style="display: none;" <?PHP } ?> ></div>
            <div class="cw-chart" id="chart<?PHP print $ChartNumber; ?>c"
              <?PHP if ($this->Scale != self::MONTHLY) { ?> style="display: none;" <?PHP } ?> ></div>
          </div>
          </div>
          <script type="text/javascript">
            jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>a').click( function(){
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>a').css('font-weight', 'bold');
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>b').css('font-weight', 'normal');
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>c').css('font-weight', 'normal');
              jQuery('#chart<?PHP print $ChartNumber; ?>a').show();
              jQuery('#chart<?PHP print $ChartNumber; ?>b').hide();
              jQuery('#chart<?PHP print $ChartNumber; ?>c').hide();
              });
            jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>b').click( function(){
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>a').css('font-weight', 'normal');
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>b').css('font-weight', 'bold');
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>c').css('font-weight', 'normal');
              jQuery('#chart<?PHP print $ChartNumber; ?>a').hide();
              jQuery('#chart<?PHP print $ChartNumber; ?>b').show();
              jQuery('#chart<?PHP print $ChartNumber; ?>c').hide();
              });
            jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>c').click( function(){
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>a').css('font-weight', 'normal');
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>b').css('font-weight', 'normal');
              jQuery('#cw-chart-button<?PHP print $ChartNumber; ?>c').css('font-weight', 'bold');
              jQuery('#chart<?PHP print $ChartNumber; ?>a').hide();
              jQuery('#chart<?PHP print $ChartNumber; ?>b').hide();
              jQuery('#chart<?PHP print $ChartNumber; ?>c').show();
              });
         </script>
        <?PHP } ?>

        <script type="text/javascript">
              <?PHP if ($this->Type == self::TYPE_DATE ) { ?>
              jQuery(document).ready(function(){
                new LineDateGraph(<?PHP print($ChartNumber); ?>,
                          <?PHP print(json_encode($this->Data)); ?>,
                          "<?PHP print($this->XLabel); ?>",
                          "<?PHP print($this->YLabel); ?>",
                          {top: <?PHP print $this->TopMargin; ?>, right: <?PHP print $this->RightMargin; ?>,
                           bottom: <?PHP print $this->BottomMargin; ?>, left: <?PHP print $this->LeftMargin; ?> },
                                  <?PHP print($this->Width); ?>, <?PHP print($this->Height); ?>, <?PHP print(json_encode($this->Legend)); ?> ); });
              <?PHP } else if ($this->Type == self::TYPE_DATE_BAR) { ?>
              jQuery(document).ready(function(){
                new BarDateGraph(
                          "<?PHP print($ChartNumber); ?>a",
                          <?PHP print(json_encode($this->Data["Daily"])); ?>,
                          "<?PHP print($this->XLabel); ?>",
                          "<?PHP print($this->YLabel); ?> (Daily)",
                          {top: <?PHP print $this->TopMargin; ?>, right: <?PHP print $this->RightMargin; ?>,
                           bottom: <?PHP print $this->BottomMargin; ?>, left: <?PHP print $this->LeftMargin; ?> },
                          <?PHP print($this->Width); ?>, <?PHP print($this->Height); ?>, <?PHP print(json_encode($this->Legend)); ?>,
                          <?PHP print(22*3600); ?>); });
             jQuery(document).ready(function(){
                    new BarDateGraph(
                          "<?PHP print($ChartNumber); ?>b",
                          <?PHP print(json_encode($this->Data["Weekly"])); ?>,
                          "<?PHP print($this->XLabel); ?>",
                          "<?PHP print($this->YLabel); ?> (Weekly)",
                          {top: <?PHP print $this->TopMargin; ?>, right: <?PHP print $this->RightMargin; ?>,
                           bottom: <?PHP print $this->BottomMargin; ?>, left: <?PHP print $this->LeftMargin; ?> },
                          <?PHP print($this->Width); ?>, <?PHP print($this->Height); ?>, <?PHP print(json_encode($this->Legend)); ?>,
                          <?PHP print(7*86400); ?>); });
            jQuery(document).ready(function(){
                     new BarDateGraph(
                          "<?PHP print($ChartNumber); ?>c",
                          <?PHP print(json_encode($this->Data["Monthly"])); ?>,
                          "<?PHP print($this->XLabel); ?>",
                          "<?PHP print($this->YLabel); ?> (Monthly)",
                          {top: <?PHP print $this->TopMargin; ?>, right: <?PHP print $this->RightMargin; ?>,
                           bottom: <?PHP print $this->BottomMargin; ?>, left: <?PHP print $this->LeftMargin; ?> },
                          <?PHP print($this->Width); ?>, <?PHP print($this->Height); ?>, <?PHP print(json_encode($this->Legend)); ?>,
                         <?PHP print(28*86400); ?>); });
              <?PHP } ?>
        </script>
        <?PHP
    }

    private $Status;

    private static $ChartNumber = 0;
    private function GetNumber(){ return self::$ChartNumber++; }

    /**
    * Helper function for summarizing data.
    * @param &$Array The aray into which data will be aggregated
    * @param $Key the index where this data should go (usually a timestamp)
    * @param $value Array of new values.
    * If no entry exists in $Array for $Key, create one.  If an entry does exist,
    * perform element-wise addition to update that entry.
    */
    private function AddToArray(&$Array, $Key, $Value)
    {
        if (!isset($Array[$Key]))
            $Array[$Key] = $Value;
        else
            $Array[$Key] = array_map( function($a,$b){ return $a+$b; },
                                      $Array[$Key], $Value );

    }

    /**
    * Helper function to convert the Graph data format to something
    * that is easier to iterate over in JS.
    * @param $Data data to convert, in Key => array(Values) format.
    * @param $IsDate bool optional, defaults to true.
    * @return Data in array( array( "X" => Key, "Y0"=> Value, ... "Yn"=>Value), ... ) format.
    */
    private function ToJsFormat($Data, $IsDate=TRUE)
    {
        $Result = array();
        foreach ($Data as $Xval => $Yvals)
        {
            $DataRow = array();


            if ($IsDate)
                $DataRow["X"] = 1000 * $Xval;
            else
                $DataRow["X"] = $Xval;

            $Count = 0;
            foreach ($Yvals as $Yval)
                $DataRow[ "Y".$Count++ ] = $Yval;

            $Result []= $DataRow;
        }

        return $Result;
    }
}
