forena-8.x-1.x-dev/src/FrxPlugin/Renderer/FrxSVGGraph.php

src/FrxPlugin/Renderer/FrxSVGGraph.php
<?php
/**
 * @file FrxSVGGraph
 * php SVG Graph generator
 *
 * @author davidmetzler
 *
 */
namespace Drupal\forena\FrxPlugin\Renderer;
use Drupal\forena\AppService;
use Drupal\forena\FrxAPI;
use Drupal\forena\FrxPlugin\Document\DocumentInterface;
use Drupal\forena\Report;
use SVGGraph;

/**
 * SVG Graphing  Renderer
 *
 * @FrxRenderer(id = "FrxSVGGraph")
 */
class FrxSVGGraph extends RendererBase {
  use FrxAPI;
  public $graph;
  public $templateName = 'Graph (svg)';
  public $xy_data = FALSE;
  public $weight;
  public $wrap_label;
  public $graphData;
  public $graphOptions;
  public $unset_attrs = array(
    'base_type',
    'crosstab_columns',
    'content',
    'sections',
    'columns',
    'style',
  );

  // Place to indicate which fields are sourced from the data.
  public $field_sources = array();

  public function __construct(Report $report, DocumentInterface $doc = NULL) {
    parent::__construct($report, $doc);
    $library = AppService::instance()->findLibrary('SVGGraph');
    if ($library) require_once $library;
  }

  /**
   * Re-architect the data into something that the graphing engine can work with
   */
  public function generateGraphData(&$data, $series, $key) {
    // Default controlling attributes
    $counts = array();
    $legend = array();
    $this->graphOptions['structure']['value'] = array();
    foreach($series as $col) {
      $this->graphOptions['structure']['value'][] = trim("$col", '{}');
    }


    $this->graphData = array();
    foreach ($data as $row) {
      $this->pushData($row, '_row');
      $trow = array();
      // Base group
      $trow['key'] =  $this->wrap_label ? wordwrap($this->report->replace($key, TRUE), $this->wrap_label) : $this->report->replace($key, TRUE);
      // Dimensions
      foreach($series as $col) {
          $val = $this->report->replace($col, TRUE);
          if ($val != '' && $val !==NULL) {
            $trow[trim("$col", '{}')] = $val;
            @$counts[trim("$col", '{}')]++;
          }
      }
      foreach($this->field_sources as $k => $src) {
        $trow[$k] = $this->report->replace($src, TRUE);
      }
      if(isset($this->field_sources['legend_entries'])) {
        $legend_str = $trow['legend_entries'];
        $legend[$legend_str] = $legend_str;
      }
      $this->popData();
      $this->graphData[] = $trow;
    }
    $this->counts = $counts;

    // Deal with rare case where legend are supposed to come from data
    if (isset($this->field_sources['legend_entries'])) {
      $this->graphOptions['legend_entries'] = array_values($legend);
    }
  }


   /**
   * Re-architect the data into something that the graphing engine can work with
   */
  public function generateGroupGraphData(&$block_data, $group, $series, $key, $dim, $sum) {
    $dim_headers = array();
    $dim_rows = array();
    $dim_values = array();
    $rows = array();
    $legend = array();
    $counts = array();
    $data = $this->report->group($block_data, $group, $sum);
    $this->graphOptions['structure'] = array('key' => $group);
    foreach ($data as $gk => $group_rows) {
      $row_copy = array_values($group_rows);
      $dims = $this->report->group($group_rows, $dim);
      $rows[$gk] = $group_rows[0];
      foreach($dims as $dk=>$r) {
        $dims = array_values($r);
        $dim_values[$dk] = $dk;
        $dim_rows[$gk][$dk] = $r[0];
      }
    }

    // Default controling attributes
    $dim_headers = array($key);
    $dim_columns = $series;

    foreach($dim_values as $dk) {
      foreach($dim_columns as $col) {
        $structure_idx = trim($dk, '{}') . trim($col, '{}');
        $this->graphOptions['structure']['value'][] = $structure_idx;
        foreach($this->field_sources as $k=>$fld) {
          $structure_idx = $dk . $k;
          $this->graphOptions['structure'][$k][] = $structure_idx;
        }
      }
    }

    $this->graphData = array();
    $gkey = '';
    foreach ($rows as $k=>$row) {
      $this->pushData($row, '_group');
      $trow = array();
      // Base group

      $gkey = $this->report->replace($group, TRUE);
      if ($this->wrap_label) $gkey = wordwrap($gkey, $this->wrap_label);
      $trow['key'] = $gkey;
      $this->popData();
      // Dimensions
      $dim_data = $dim_rows[$k];
      foreach($dim_values as $dk) {
        $dim_row = isset($dim_data[$dk]) ? $dim_data[$dk] : array();
        $this->pushData($dim_row, '_dim');
        foreach($dim_columns as $col) {

          $val = $this->report->replace($col, TRUE);
          if ($val !== '' && $val !== NULL) {
            $trow[trim($dk, '{}') . trim($col, '{}')] = $val;
            @$counts[trim($dk, '{}') . trim($col, '{}')]++;
          }
          foreach($this->field_sources as $fk => $src) {
            $trow[$dk . $fk] = $this->report->replace($src, TRUE);
            if (isset($this->field_sources['legend_entries'])) {
              $legend_str = $this->report->replace($this->field_sources['legend_entries']);
              $legend[$legend_str] = $legend_str;
            }

          }
        }


        $this->popData();
      }
      $this->graphData[] = $trow;
      $this->counts = $counts;

    }

    // Deal with rare case where legend are supposed to come from data
    if (isset($this->field_sources['legend_entries'])) {

      $this->graphOptions['legend_entries'] = array_values($legend);
    }
    $this->graphOptions['structure']['key'] = 'key';
    return $this->graphData;
  }


  public function prepareGraph() {
    $skin_options = $this->getDataContext('skin');
    $options = isset($skin_options['FrxSVGGraph']) ? $skin_options['FrxSVGGraph'] : array();
    $attributes = $this->mergedAttributes();
    // Default in xpath for backward compatibility
    // Legacy options.  New charts should be generated using FrxAPI:attribute syntax
    if (isset($attributes['options'])) {
      $attr_options = array();
      parse_str($attributes['options'], $attr_options);
      unset($attributes['options']);
      foreach ($attr_options as $key => $value) {
        $options[$key] = $this->report->replace($value, TRUE);
      }
    }
    else {
      $options = array_merge($options, $attributes);
    }

    // Main Graphing options
    $path = $this->extract('path', $options);
    if (!$path) $path = $this->extract('xpath', $options); //Deprecated
    if (!$path) $path = '*';
    $group = $this->extract('group', $options);
    $series = @(array)$attributes['series'];
    $sums = @(array)$attributes['sum'];
    $key = @$attributes['key'];
    $options['key'] = $key;
    $dim = $this->extract('dim', $options);
    $this->wrap_label = (int)$this->extract('wrap_label', $options);
    if (!$key) $key = @$options['seriesx']; // Deprecated

    // Determine basic data to iterate.
    $data = $this->currentDataContext();
    if (is_object($data)) {
      if (method_exists($data, 'xpath')) {
        $nodes = $data->xpath($path);
      }
      else {
        $nodes = $data;
      }
    }
    else {
      $nodes = (array)$data;
    }

    // Force structured data
    $options['structured_data'] = TRUE;
    $options['structure'] = array('key' => 'key');

    // Default in american colour;
    $this->field_sources = array();
    if (isset($options['color'])) $options['colour'] = $options['color'];

    // Find out data that is designed to be sepecif to series.
    $this->field_sources = array();

    foreach ($options as $fk => $opt) {
      if ($fk != 'value' && $fk != 'key' && $opt && !is_array($opt) && strpos($options[$fk], '{')!==FALSE) {
        $this->field_sources[$fk] = $opt;
        $options['structure'][$fk] = $fk;
      }
    }
    if (isset($attributes['height'])) $options['height'] = $this->report->replace($attributes['height'], TRUE);
    if (isset($attributes['width'])) $options['width'] = $this->report->replace($attributes['width'], TRUE);
    if (isset($options['legend_entries']) && !isset($options['label']) && !isset($options['show_labels'])) {
      $options['show_labels'] = FALSE;
    }


    $this->graphOptions = $options;

    if ($group) {
      $this->generateGroupGraphData($nodes, $group, $series, $key, $dim, $sums);
    }
    else {
      $this->generateGraphData($nodes, $series, $key);
    }

    if (isset($this->graphOptions['legend_entries']) && !is_array($this->graphOptions['legend_entries'])) {
      $this->graphOptions['legend_entries'] = explode('|', $this->graphOptions['legend_entries']);
    }

  }


  /**
   * Render the graph.
   * @return string
   *   Rendered SVG. 
   */
  public function render() {
    // Get data from source
    $output = '';
    $this->prepareGraph();
    $type = $this->graphOptions['type'];
    if ($this->graphData && $this->validSeries()) {
      $output = $this->renderChart($type);
    }

    return $output;
  }

  static function graphTypes() {
    return array(
      'BarGraph' => array('type' => 'Bar Graph', 'style' => 'Simple' ),
      'Bar3DGraph' => array('type' => 'Bar Graph', 'style' => '3D' ),
      'StackedBarGraph' => array('type' => 'Bar Graph', 'style' => 'Stacked'),
      'GroupedBarGraph' => array('type' => 'Bar Graph', 'style' => 'Grouped'),
      'BarAndLineGraph' => array('type' => 'Bar and Line Graph', 'style' => 'Grouped'),
      'CylinderGraph' => array('type' => 'Bar Graph', 'style' => 'Cylinder'),
      'StackedCylinderGraph' => array('type' => 'Bar Graph', 'style' => 'Stacked Cylinder'),
      'GroupedCylinderGraph' =>  array('type' => 'Bar Graph', 'style' => 'Grouped Cylinder'),
      'PieGraph' => array('type' => 'Pie Chart', 'style' => 'Simple'),
      'Pie3DGraph' => array('type' => 'Pie Chart', 'style' => '3D'),
      'HorizontalBarGraph' => array('type' => 'Bar Graph', 'style' => 'Horizontal'),
      'LineGraph' =>array('type' => 'Line Graph', 'style' => 'Simple'),
      'MultiLineGraph' => array('type' => 'Line Graph', 'style' => 'Multi'),
      'ScatterGraph' => array('type' => 'Scatter Plot', 'style' => 'Simple', 'xaxis' => TRUE),
      'MultiScatterGraph' => array('type' => 'Scatter Plot', 'style' => '3D', 'xaxis' => TRUE),
      'RadarGraph' => array('type' => 'Radar Graph', 'style' => 'Simple'),
      'MultiRadarGraph' => array('type' => 'Radar Graph', 'style' => 'Multi'),
    );
  }

  static function graphOptions() {
    $data = FrxSVGGraph::graphTypes();
    foreach($data as $key => $value) {
      $type[$value['type']] = $value['type'];
      $style[$value['type']][$key] = $value['style'];
    }
    return array('types' => $type, 'styles' => $style);
  }

  function renderChart($type ) {

    $type = strtolower($type);
    // Legacy sustitions for backcward compatibility.
    if ($type == 'piechart') $type = 'piegraph';
    if ($type == 'scatterplot') $type = 'scattergraph';
    if ($type == 'multiscatterplot') $type = 'multiscattergraph';

    // Newly defined types
    $graph_types = FrxSVGGraph::graphTypes();

    // Build map for array types.
    $lower_graphs_types = array_change_key_case($graph_types);
    $graph_classes = array_combine(array_keys($lower_graphs_types), array_keys($graph_types));
    if (isset($graph_classes[$type])) {
      $class = $graph_classes[$type];
      $output = $this->renderGraph($class);
    }
    return $output;
  }


  /**
   * Checks wheter we have a valid series.
   * @return bool
   *   TRUE indicates the series if valid.
   */
  public function validSeries() {
    $valid = TRUE;
    $removed = array();
    if (isset($this->graphOptions['structure']['value'])) {
      foreach ($this->graphOptions['structure']['value'] as $k=>$series) {
        if (!isset($this->counts[$series])) {
          // remove empty series.
          unset($this->graphOptions['structure']['value'][$k]);
          $removed[] = $k;
        }
      }

      if ($removed) {
        $this->graphOptions['structure']['value'] = array_values($this->graphOptions['structure']['value']);
        foreach($this->graphOptions as $option => $data) {
          if (is_array($data)) {
            $modified = FALSE;
            foreach($removed as $k) {
              if (isset($data[$k])) {
                unset($this->graphOptions[$option][$k]);
                $modified = TRUE;
              }
            }
            if ($modified) {
              $this->graphOptions[$option] = array_values($this->graphOptions[$option]);
            }
          }
        }
        if (!$this->graphOptions['structure']['value']) $valid = FALSE;
      }
    }
    return $valid;
  }


  function renderGraph($type) {
    // IF we don't have a library give up

    static $jsinc = '';

    $options = $this->replaceTokens($this->graphOptions);
    $data = $this->graphData;

    if (!isset($options['scatter_2d']) && ($type == 'ScatterGraph' || $type=='MultiScatterGraph') && $this->xy_data && !isset($options['scatter_2d'])) {
      $options['scatter_2d'] = TRUE;
    }
    else {
      $options['scatter_2d'] = (bool) @$options['scatter_2d'];
    }
    // Sanitize label option for SVG Graph 2.20 and greater
    if (isset($options['label']) && !is_array($options['label'])) {
      unset($options['label']);
    }
    $width = (@$options['width']) ? @$options['width'] : 600;
    $height = (@$options['height']) ? @$options['height'] : 400;

    // If the library isn't installed then quit.
    $this->graphOptions = $options;
    if (!class_exists('\SVGGraph')) return NULL; 
    $graph = new SVGGraph($width, $height, $options);
    $this->graph = $graph;
    $graph->Values($data);
    if (isset($options['colour']) && is_array($options['colour'])) {
      $graph->Colours($options['colour']);
    }
    // Generate the graph
    $output = $graph->Fetch($type, FALSE);
    // Add a viewbox to be compatible with Prince PDF generation.
    if (!@$options['noviewbox']) $options['auto_fit'] = true; 
    if (!@$options['noviewbox'] && !strpos($output, 'viewBox')) $output = str_replace('<svg width', "<svg viewBox='0 0 $width $height' width", $output);


    if (!$jsinc && $this->documentManager()->getDocumentType() == 'drupal') {
      if (@!$options['no_js']) {
        //$js =  $graph->FetchJavascript();
        //$output .= $js;
      }
      $jsinc = TRUE;
    }

    return $output;
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc