From fa5fe78d553a53fe7c286c5756366a2d47a342ca Mon Sep 17 00:00:00 2001
From: Bruno Massa <brmassa@67164.no-reply.drupal.org>
Date: Tue, 12 May 2009 20:05:50 +0000
Subject: [PATCH] Improvements: * Finally a Views integration worthy to
 mention! Based on the Karen Stevenson's code. * Note that the Views
 integration now depends on Views Calc module * Also note that Views Calc
 ALREADY has the same code, but its not fair to make her to maintain such
 unrelated code on Views Calc module.

---
 views/charts.views.inc              |  28 +--
 views/charts_plugin_style_chart.inc | 296 ++++++++++++++++------------
 2 files changed, 187 insertions(+), 137 deletions(-)

diff --git a/views/charts.views.inc b/views/charts.views.inc
index 5aaee39..474956e 100644
--- a/views/charts.views.inc
+++ b/views/charts.views.inc
@@ -3,31 +3,35 @@
 /**
  * @author Bruno Massa http://drupal.org/user/67164
  * @file
- * Vinews integration with Charts.
+ * Views integration with Charts.
  */
 
 /**
- * Implementation of hook_views_plugins().
- *
- * Define charts style for Views.
- */
+* Implementation of hook_views_plugins().
+*
+* Define charts style for Views.
+*/
 function charts_views_plugins() {
+  // Views support is only possible if Views Calc module is enabled. It
+  // is responsible for turn the Views values into a aggregates.
+  if (!module_exists('views_calc')) {
+    return;
+  }
+
   return array(
     'module' => 'charts',
     'style'  => array(     // Declare the charts style plugin
       'chart' => array(
+        'handler'         => 'charts_plugin_style_chart',
+        'help'            => t('Displays the content in several Chart styles.'),
         'path'            => drupal_get_path('module', 'charts') .'/views',
+        'parent'          => 'parent',
         'title'           => t('Chart'),
-        //'theme'         => 'views_view_chart',
-        'help'            => t('Displays the content in several Chart styles.'),
-        'handler'         => 'charts_plugin_style_chart',
-        'uses row plugin' => FALSE,
+        'type'            => 'normal',
         'uses fields'     => TRUE,
         'uses options'    => TRUE,
-        'type'            => 'normal',
-        'help topic'      => 'style-chart',
+        'uses row plugin' => FALSE,
       ),
     )
   );
 }
-
diff --git a/views/charts_plugin_style_chart.inc b/views/charts_plugin_style_chart.inc
index 994623d..903c1c5 100644
--- a/views/charts_plugin_style_chart.inc
+++ b/views/charts_plugin_style_chart.inc
@@ -1,9 +1,10 @@
 <?php
 // $Id$
-
 /**
  * @file
  * Contains the chart style plugin.
+ * @author Bruno Massa      http://drupal.org/user/67164
+ * @author Karen Stevenson  http://drupal.org/user/45874
  */
 
 /**
@@ -12,24 +13,30 @@
  * @ingroup views_style_plugins
  */
 class charts_plugin_style_chart extends views_plugin_style {
-  // Set default options.
+  /**
+   * Set default options.
+   */
   function options(&$options) {
-    $options['format'] = 'pie2D';
-    $options['height'] = 200;
-    $options['width'] = 400;
-    $options['color'] = 'ffffff';
-    //$options['columns'] = array();
-    //$options['default'] = '';
-    //$options['info'] = array();
-    $options['conversion'] = 'rows';
+    $options['format']            = 'pie2D';
+    $options['height']            = 200;
+    $options['width']             = 400;
+    $options['color']             = 'ffffff';
+    $options['aggregation_field'] = '';
+    $options['calc_fields']       = array();
+    $options['calc']              = 'COUNT';
+    $options['precision']         = 2;
   }
 
-  // Generate a form for setting options.
+  /**
+   * Generate a form for setting options.
+   */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+
     $form['format'] = array(
       '#type'    => 'select',
       '#title'   => t('Chart format'),
-      '#options' => array(
+      '#options'       => array(
         'line2D'  => t('Line 2D'),
         'hbar2D'  => t('Horizontal Bar 2D'),
         'vbar2D'  => t('Vertical Bar 2D'),
@@ -41,127 +48,109 @@ class charts_plugin_style_chart extends views_plugin_style {
       '#default_value' => $this->options['format'],
     );
     $form['height'] = array(
-      '#type'          => 'textfield',
-      '#title'         => t('Chart height'),
-      '#default_value' => $this->options['height'],
+      '#type'           => 'textfield',
+      '#title'          => t('Chart height'),
+      '#default_value'  => $this->options['height'],
+      '#required'       => TRUE, // Google charts breaks if it is empty.
+      '#description'    => t('An integer value, the number of pixels of height for this chart.'),
     );
     $form['width'] = array(
-      '#type'          => 'textfield',
-      '#title'         => t('Chart width'),
-      '#default_value' => $this->options['width'],
+      '#type'           => 'textfield',
+      '#title'          => t('Chart width'),
+      '#default_value'  => $this->options['width'],
+      '#required'       => TRUE, // Google charts breaks if it is empty.
+      '#description'    => t('An integer value, the number of pixels of width for this chart.'),
     );
     $form['color'] = array(
-      '#type'          => 'textfield',
-      '#title'         => t('Background color'),
-      '#default_value' => $this->options['color'],
-      '#description'   => t('In hexadecimal format (RRGGBB). Do not use the # symbol.'),
-    );
-    $form['conversion'] = array(
-      '#type'    => 'radios',
-      '#title'   => t('Conversion type'),
-      '#options' => array(
-        'rows'   => t('Display numbers from every row'),
-        'sum'    => t('Display sum of different values from one column'),
-      ),
-      '#default_value' => $this->options['conversion'],
-      '#description'   => t('In the first option every row will be a new x value in the chart. In the second option every different value in one column will be a new x value in the chart.'),
+      '#type'           => 'textfield',
+      '#title'          => t('Background color'),
+      '#default_value'  => $this->options['color'],
+      '#description'    => t('In hexadecimal format (RRGGBB). Do not use the # symbol.'),
+      '#required'       => TRUE, // Google charts breaks if it is empty.
     );
     $form['show_legend'] = array(
-      '#type'          => 'checkbox',
-      '#title'         => t('Show legend'),
-      '#default_value' => 1,
-      '#description'   => t('Display legend next to the chart.'),
+      '#type'           => 'checkbox',
+      '#title'          => t('Show legend'),
+      '#default_value'  => $this->options['show_legend'],
+      '#description'    => t('Display legend next to the chart.'),
     );
+
+    $form['aggregation_field'] = array(
+      '#type'           => 'select',
+      '#title'          => t('Aggregation field'),
+      '#options'        => $this->aggregated_field_options(),
+      '#default_value'  => $this->options['aggregation_field'],
+      '#description'    => t('Select a field to aggreagate the results on.')
+      );
+    // TODO Charts module cannot currently handle more than one series,
+    // update Multiple to TRUE if that changes.
+    $form['calc_fields'] = array(
+      '#type'           => 'select',
+      '#title'          => t('Computation field'),
+      '#options'        => $this->aggregated_field_options(),
+      '#default_value'  => $this->calc_fields(),
+      '#multiple'       => FALSE,
+      '#description'    => t('Select field to perform computations on.')
+      );
+    $form['calc'] = array(
+      '#type'           => 'select',
+      '#title'          => t('Computation to perform'),
+      '#options'        => $this->calc_options(),
+      '#default_value'  => $this->options['calc'],
+      );
+    $form['precision'] = array(
+      '#type'           => 'select',
+      '#title'          => t('Precision'),
+      '#options'        => range(0, 4),
+      '#default_value'  => $this->options['precision'],
+      '#description'    => t('Decimal points to use in computed values.'),
+      );
   }
 
-  // Define and display a test chart.
-  function render() {
-    // Scan all Views data and insert them into a series.
+  /**
+   * Views Calc operation.
+   */
+  function calc_options() {
+    return array(
+      ''      => t('None'),
+      'SUM'   => t('Sum'),
+      'COUNT' => t('Count'),
+      'AVG'   => t('Average'),
+      'MIN'   => t('Minimum'),
+      'MAX'   => t('Maximum'),
+    );
+  }
 
-    if ($this->options['conversion'] == 'rows') {
-      // Get columns.
-      foreach ($this->view->field as $key => $field) {
-        if ($this->view->field[$key]->options['label'] == 'chart label') {
-          $chart_label_column = $key;
-          continue;
-        }
-        if (!$field->options['exclude']) {
-          $data[$key]['#legend'] = ($this->options['show_legend']) ? $field->options['label'] : NULL;
-        }
+  /**
+   * Create an options array of available fields from this view.
+   */
+  function aggregated_field_options() {
+    $field_names = array();
+    $handlers = $this->display->handler->get_handlers('field');
+    foreach ($handlers as $field => $handler) {
+      if ($label = $handler->label()) {
+        $field_names[$field] = $label;
       }
-
-      // Get values from rows.
-      foreach ($this->view->result as $index => $row) {
-        if (!isset($chart_label_column)) {
-          $label = $index;
-        }
-        foreach ($this->view->field as $key => $field) {
-          if ($chart_label_column == $key) {
-            $label = theme_views_view_field($this->view, $this->view->field[$key], $row);
-            continue;
-          }
-          if ($this->view->field[$key]->options['exclude']) {
-            continue;
-          }
-          $field_alias = $this->view->field[$key]->field_alias;
-          if (isset($this->view->result[$index]->$field_alias)) {
-            // Try to get the value from the result.
-            $field_value = $this->view->result[$index]->$field_alias;
-          }
-          else {
-            // Try to get the value with theme function.
-            $field_value = theme_views_view_field($this->view, $this->view->field[$key], $row);
-          }
-          if (is_numeric($field_value)) {
-            $data[$key][$index]['#value'] = $field_value;
-            $data[$key][$index]['#label'] = $label;
-          }
-        }
+      else {
+        $field_names[$field] = $handler->ui_name();
       }
     }
+    return $field_names;
+  }
 
+  /**
+   * Make sure calc_fields is always an array, even when not multiple.
+   */
+  function calc_fields() {
+    $calc_fields = (array) $this->options['calc_fields'];
+    return array_values($calc_fields);
+  }
 
-    if ($this->options['conversion'] == 'sum') {
-      // Get fields.
-      foreach ($this->view->field as $key => $field) {
-        if (!$this->view->field[$key]->options['exclude']) {
-          $data[$key]['#legend'] = ($this->options['show_legend']) ? $this->view->field[$key]->options['label'] : NULL;
-        }
-      }
-
-      // Get values from rows.
-      foreach ($this->view->result as $row) {
-        foreach ($this->view->field as $key => $field) {
-          if ($this->view->field[$key]->options['exclude']) {
-            continue;
-          }
-          $field_value = theme_views_view_field($this->view, $this->view->field[$key], $row);
-          if (!isset($data[$key][$field_value]['#value'])) {
-            $data[$key][$field_value]['#label'] = $field_value;
-            $data[$key][$field_value]['#value'] = 1;
-          }
-          else {
-            $data[$key][$field_value]['#value']++;
-          }
-        }
-      }
-
-      // Convert index, because Charts module only accepts numeric index on series.
-      // Don't really understund why is this restriction.
-      foreach ($data as $key => $series) {
-        $index = 0;
-        foreach ($series as $key2 => $value) {
-          if ($key2 == '#legend') {
-            continue;
-          }
-          $data[$key][$index] = $value;
-          unset($data[$key][$key2]);
-          $index++;
-        }
-      }
-
-    }
-
+  /**
+   * Define and display a chart from the grouped values.
+   */
+  function render() {
+    // Scan all Views data and insert them into a series.
 
     // Get chart settings from options form.
     $chart = array(
@@ -169,18 +158,75 @@ class charts_plugin_style_chart extends views_plugin_style {
       '#height' => $this->options['height'],
       '#width'  => $this->options['width'],
       '#color'  => $this->options['color'],
+      '#title'  => $this->view->get_title(),
     );
 
-    // Use the view title as the chart title.
-    $chart['#title'] = $this->view->get_title();
-
-    // Insert series into the chart array.
-    foreach ($data as $series) {
-      $chart[] = $series;
+    // Get values from rows.
+    foreach ($this->calc_fields() as $calc) {
+      foreach ($this->view->result as $row) {
+        foreach ($this->view->field as $key => $field) {
+          if ($key == $this->options['aggregation_field']) {
+            $legend_field = array_key_exists($calc, $this->view->field) ? $this->view->field[$calc] : NULL;
+            $legend = !empty($legend_field->options['label']) ? $legend_field->options['label'] : NULL;
+            if ($this->options['show_legend']) {
+              $data[$calc]['#legend'] = $legend;
+            }
+            $value['#label'] = strip_tags(theme_views_view_field($this->view, $this->view->field[$key], $row)); // .': '. $row->$calc;
+            $value['#value'] = $row->$calc;
+            $chart[$calc][] = $value;
+          }
+        }
+      }
     }
 
     // Print the chart.
     return charts_chart($chart);
   }
-}
 
+  /**
+   * Custom SQL query change.
+   */
+  function query() {
+    parent::query();
+
+    // Clear the fields out, we'll replace them with calculated values.
+    $this->view->query->clear_fields();
+    // Clear out any sorting, it can create unexpected results
+    // when Views adds aggregation values for the sorts.
+    $this->view->query->orderby = array();
+
+    // Add the grouping information to the query.
+    // Field setting of array('aggregate' => TRUE) tells Views not to force
+    // another aggregation in for this field.
+
+    foreach ($this->view->field as $field) {
+      $query_field = substr($field->field, 0, 3) == 'cid' ? $field->definition['calc'] : $field->table .'.'. $field->field;
+      $query_alias = $field->field_alias;
+
+      // Add the aggregation.
+      if ($field->field == $this->options['aggregation_field']) {
+        $this->view->query->add_orderby(NULL, NULL, 'asc', $query_alias);
+        $this->view->query->add_groupby($query_field);
+        if (substr($field->field, 0, 3) == 'cid') {
+          $this->view->query->add_field(NULL, $query_field, $field->field, array('aggregate' => TRUE));
+        }
+        else {
+          $this->view->query->add_field($field->table, $field->field, NULL, array('aggregate' => TRUE));
+        }
+      }
+      // Add computed values.
+      if (in_array($field->field, $this->calc_fields())) {
+        $sql = "ROUND(". $this->options['calc'] ."($query_field), ". $this->options['precision'] .")";
+        $this->view->query->add_field(NULL, $sql, $field->field, array('aggregate' => TRUE));
+
+        // TODO This part is not relationship-safe, needs additional work
+        // to join in the right table if the computation is done
+        // on a field that comes from a relationship.
+
+        // Make sure the table with the right alias name is available
+        // (it might have been dropped during Views optimizations.)
+        $this->view->query->add_table($field->table);
+      }
+    }
+  }
+}
-- 
GitLab