franck@0: 'select', franck@0: '#title' => t('Chart format'), franck@0: '#options' => array( franck@0: 'line2D' => t('Line 2D'), franck@0: 'hbar2D' => t('Horizontal Bar 2D'), franck@0: 'vbar2D' => t('Vertical Bar 2D'), franck@0: 'pie2D' => t('Pie 2D'), franck@0: 'pie3D' => t('Pie 3D'), franck@0: 'venn' => t('Venn'), franck@0: 'scatter' => t('Scatter Plot') franck@0: ), franck@0: '#default_value' => $this->options['format'], franck@0: ); franck@0: $form['height'] = array( franck@0: '#type' => 'textfield', franck@0: '#title' => t('Chart height'), franck@0: '#default_value' => $this->options['height'], franck@0: '#required' => TRUE, // Google charts breaks if it is empty. franck@0: '#description' => t('An integer value, the number of pixels of height for this chart.'), franck@0: ); franck@0: $form['width'] = array( franck@0: '#type' => 'textfield', franck@0: '#title' => t('Chart width'), franck@0: '#default_value' => $this->options['width'], franck@0: '#required' => TRUE, // Google charts breaks if it is empty. franck@0: '#description' => t('An integer value, the number of pixels of width for this chart.'), franck@0: ); franck@0: $form['color'] = array( franck@0: '#type' => 'textfield', franck@0: '#title' => t('Background color'), franck@0: '#default_value' => $this->options['color'], franck@0: '#description' => t('In hexadecimal format (RRGGBB). Do not use the # symbol.'), franck@0: '#required' => TRUE, // Google charts breaks if it is empty. franck@0: ); franck@0: $form['show_legend'] = array( franck@0: '#type' => 'checkbox', franck@0: '#title' => t('Show legend'), franck@0: '#default_value' => $this->options['show_legend'], franck@0: '#description' => t('Display legend next to the chart.'), franck@0: ); franck@0: franck@0: $form['aggregation_field'] = array( franck@0: '#type' => 'select', franck@0: '#title' => t('Aggregation field'), franck@0: '#options' => $this->aggregated_field_options(), franck@0: '#default_value' => $this->options['aggregation_field'], franck@0: '#description' => t('Select a field to aggreagate the results on.') franck@0: ); franck@0: // TODO Charts module cannot currently handle more than one series, franck@0: // update Multiple to TRUE if that changes. franck@0: $form['calc_fields'] = array( franck@0: '#type' => 'select', franck@0: '#title' => t('Computation field'), franck@0: '#options' => $this->aggregated_field_options(), franck@0: '#default_value' => $this->calc_fields(), franck@0: '#multiple' => FALSE, franck@0: '#description' => t('Select field to perform computations on.') franck@0: ); franck@0: $form['calc'] = array( franck@0: '#type' => 'select', franck@0: '#title' => t('Computation to perform'), franck@0: '#options' => $this->calc_options(), franck@0: '#default_value' => $this->options['calc'], franck@0: ); franck@0: $form['precision'] = array( franck@0: '#type' => 'select', franck@0: '#title' => t('Precision'), franck@0: '#options' => range(0, 4), franck@0: '#default_value' => $this->options['precision'], franck@0: '#description' => t('Decimal points to use in computed values.'), franck@0: ); franck@0: } franck@0: franck@0: function calc_options() { franck@0: return array( franck@0: '' => t('None'), franck@0: 'SUM' => t('Sum'), franck@0: 'COUNT' => t('Count'), franck@0: 'AVG' => t('Average'), franck@0: 'MIN' => t('Minimum'), franck@0: 'MAX' => t('Maximum'), franck@0: ); franck@0: } franck@0: franck@0: /** franck@0: * Create an options array of available fields from this view. franck@0: */ franck@0: function aggregated_field_options() { franck@0: $field_names = array(); franck@0: $handlers = $this->display->handler->get_handlers('field'); franck@0: foreach ($handlers as $field => $handler) { franck@0: if ($label = $handler->label()) { franck@0: $field_names[$field] = $label; franck@0: } franck@0: else { franck@0: $field_names[$field] = $handler->ui_name(); franck@0: } franck@0: } franck@0: return $field_names; franck@0: } franck@0: franck@0: /** franck@0: * Make sure calc_fields is always an array, even when not multiple. franck@0: */ franck@0: function calc_fields() { franck@0: $calc_fields = (array) $this->options['calc_fields']; franck@0: return array_values($calc_fields); franck@0: } franck@0: franck@0: // Define and display a chart from the grouped values. franck@0: function render() { franck@0: // Scan all Views data and insert them into a series. franck@0: $data = array(); franck@0: franck@0: // Get values from rows. franck@0: foreach ($this->calc_fields() as $calc) { franck@0: foreach ($this->view->result as $row) { franck@0: foreach ($this->view->field as $key => $field) { franck@0: if ($key == $this->options['aggregation_field']) { franck@0: $legend_field = array_key_exists($calc, $this->view->field) ? $this->view->field[$calc] : NULL; franck@0: $legend = !empty($legend_field->options['label']) ? $legend_field->options['label'] : NULL; franck@0: if ($this->options['show_legend']) { franck@0: $data[$calc]['#legend'] = $legend; franck@0: } franck@0: $value['#label'] = strip_tags(theme_views_view_field($this->view, $this->view->field[$key], $row)); // .': '. $row->$calc; franck@0: $value['#value'] = $row->$calc; franck@0: $data[$calc][] = $value; franck@0: } franck@0: } franck@0: } franck@0: } franck@0: franck@0: // Get chart settings from options form. franck@0: $legend_field = $this->view->field[$this->options['aggregation_field']]; franck@0: $chart = array( franck@0: '#type' => $this->options['format'], franck@0: '#height' => $this->options['height'], franck@0: '#width' => $this->options['width'], franck@0: '#color' => $this->options['color'], franck@0: ); franck@0: franck@0: // Use the view title as the chart title. franck@0: $chart['#title'] = $this->view->get_title(); franck@0: franck@0: // Insert series into the chart array. franck@0: foreach ($data as $series) { franck@0: $chart[] = $series; franck@0: } franck@0: franck@0: // Print the chart. franck@0: return charts_chart($chart); franck@0: } franck@0: franck@0: function query() { franck@0: parent::query(); franck@0: franck@0: // Clear the fields out, we'll replace them with calculated values. franck@0: $this->view->query->clear_fields(); franck@0: // Clear out any sorting, it can create unexpected results franck@0: // when Views adds aggregation values for the sorts. franck@0: $this->view->query->orderby = array(); franck@0: franck@0: // Add the grouping information to the query. franck@0: // Field setting of array('aggregate' => TRUE) tells Views not to force franck@0: // another aggregation in for this field. franck@0: franck@0: foreach ($this->view->field as $field) { franck@0: $query_field = substr($field->field, 0, 3) == 'cid' ? $field->definition['calc'] : $field->table .'.'. $field->field; franck@0: $query_alias = $field->field_alias; franck@0: franck@0: // Add the aggregation. franck@0: if ($field->field == $this->options['aggregation_field']) { franck@0: $this->view->query->add_orderby(NULL, NULL, 'asc', $query_alias); franck@0: $this->view->query->add_groupby($query_field); franck@0: if (substr($field->field, 0, 3) == 'cid') { franck@0: $this->view->query->add_field(NULL, $query_field, $field->field, array('aggregate' => TRUE)); franck@0: } franck@0: else { franck@0: $this->view->query->add_field($field->table, $field->field, NULL, array('aggregate' => TRUE)); franck@0: } franck@0: } franck@0: // Add computed values. franck@0: if (in_array($field->field, $this->calc_fields())) { franck@0: $sql = "ROUND(". $this->options['calc'] ."($query_field), ". $this->options['precision'] .")"; franck@0: $this->view->query->add_field(NULL, $sql, $field->field, array('aggregate' => TRUE)); franck@0: franck@0: // TODO This part is not relationship-safe, needs additional work franck@0: // to join in the right table if the computation is done franck@0: // on a field that comes from a relationship. franck@0: franck@0: // Make sure the table with the right alias name is available franck@0: // (it might have been dropped during Views optimizations.) franck@0: $this->view->query->add_table($field->table); franck@0: } franck@0: } franck@0: } franck@0: }