summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathan Haug2013-07-29 04:37:24 (GMT)
committer Nathan Haug2013-07-29 04:54:07 (GMT)
commitc662d04aff748d3a16040460d1f6b95e21ac134e (patch)
tree6b398a404aab3fb21f84e8cf6cbd17e450205201
parent390084f1947e1c9f031c5d07ff6c65f238b91d76 (diff)
Issue #2052429: Make multiple series charts easier to create through Views.7.x-2.0-beta4
-rw-r--r--README.txt28
-rw-r--r--charts.info1
-rw-r--r--js/charts.admin.js4
-rw-r--r--views/charts.views.inc16
-rw-r--r--views/charts_plugin_display_chart.inc130
-rw-r--r--views/charts_plugin_style_chart.inc137
6 files changed, 308 insertions, 8 deletions
diff --git a/README.txt b/README.txt
index 33a4474..9290dc7 100644
--- a/README.txt
+++ b/README.txt
@@ -88,6 +88,34 @@ Tip: You may find it easier to start with a "Table" display and convert it to a
chart display after setting up the data. It can be easier to visualize what
the result of the chart will be if it's been laid out in a table first.
+Creating Multiple Series and Combo Charts in the UI
+---------------------------------------------------
+
+When using Views to build your charts, you may find it difficult to retrieve
+more than a single set of data generated by a COUNT() query. For example if you
+wanted to retrieve the age of all your site users, but display "Male" and
+"Female" values in a column chart at the same time, constructing the underlying
+table of data is quite difficult.
+
+To solve this problem, you can combine multiple charts on top of each other. The
+"parent" chart provides the global information, such as the height, width,
+title, and other properties. Charts that are "children" provide only data and
+(optionally) a secondary axis. After you've assembled the first series of data
+in your chart according to the instructions in the "Creating Charts in the UI"
+section, add a new display to the same view of the type "Chart Add-on". The
+"Chart Add-on" type is added the same way you would add a new Page or Block
+display, from the "+ Add" menu at the top of the view configuration.
+
+After this new display has been added, find the setting for "Combine with parent
+chart" and change this value to point at the parent chart you have already
+assembled. Then adjust the settings for the child chart to pull in different
+data (often by overriding the filter settings). Now you can go back to your
+parent display, and see that the results from the child chart have been
+merged into the results from the parent chart. You can even use this approach
+to combine different types of charts, such as a line chart over the top of
+a column chart. Note that not all chart types can be combined together and
+invalid combinations may cause your chart to throw errors.
+
Create Charts through the API
-----------------------------
diff --git a/charts.info b/charts.info
index 193d8a9..2bb80e7 100644
--- a/charts.info
+++ b/charts.info
@@ -5,3 +5,4 @@ core = 7.x
php = 5.2
files[] = views/charts_plugin_style_chart.inc
+files[] = views/charts_plugin_display_chart.inc
diff --git a/js/charts.admin.js b/js/charts.admin.js
index 74c2f09..af61652 100644
--- a/js/charts.admin.js
+++ b/js/charts.admin.js
@@ -10,7 +10,9 @@ Drupal.behaviors.chartsAdmin.attach = function(context, settings) {
$(context).find('.form-radios.chart-type-radios').once('charts-axis-inverted-processed', function() {
// Manually attach collapsible fieldsets first.
- Drupal.behaviors.collapse.attach(context, settings);
+ if (Drupal.behaviors.collapse) {
+ Drupal.behaviors.collapse.attach(context, settings);
+ }
var xAxisLabel = $('fieldset.chart-xaxis .fieldset-title').html();
var yAxisLabel = $('fieldset.chart-yaxis .fieldset-title').html();
diff --git a/views/charts.views.inc b/views/charts.views.inc
index d4e54a9..eaf21a2 100644
--- a/views/charts.views.inc
+++ b/views/charts.views.inc
@@ -23,6 +23,22 @@ function charts_views_plugins() {
'uses grouping' => FALSE,
'type' => 'normal',
);
+ $plugins['style']['chart_extension'] = $plugins['style']['chart'];
+ $plugins['style']['chart_extension']['type'] = 'chart';
+
+ $plugins['display']['chart'] = array(
+ 'title' => t('Chart add-on'),
+ 'admin' => t('Chart add-on'),
+ 'help' => t('Add chart data to a new or existing chart.'),
+ 'handler' => 'charts_plugin_display_chart',
+ 'theme' => 'views_view',
+ 'register theme' => FALSE,
+ 'use ajax' => FALSE,
+ 'use pager' => FALSE,
+ 'use more' => FALSE,
+ 'accept attachments' => FALSE,
+ 'type' => 'chart',
+ );
return $plugins;
}
diff --git a/views/charts_plugin_display_chart.inc b/views/charts_plugin_display_chart.inc
new file mode 100644
index 0000000..7d7bb29
--- /dev/null
+++ b/views/charts_plugin_display_chart.inc
@@ -0,0 +1,130 @@
+<?php
+/**
+ * @file
+ * Contains the Chart display type (similar to Page, Block, Attachment, etc.)
+ */
+
+/**
+ * Display plugin to attach multiple chart configurations to the same chart.
+ *
+ * @ingroup views_style_plugins
+ */
+class charts_plugin_display_chart extends views_plugin_display {
+ function get_style_type() {
+ return 'chart';
+ }
+
+ function option_definition() {
+ $options = parent::option_definition();
+
+ // Overrides of standard options.
+ $options['style_plugin']['default'] = 'chart_extension';
+ $options['row_plugin']['default'] = 'fields';
+ $options['defaults']['default']['style_plugin'] = FALSE;
+ $options['defaults']['default']['style_options'] = FALSE;
+ $options['defaults']['default']['row_plugin'] = FALSE;
+ $options['defaults']['default']['row_options'] = FALSE;
+
+ $options['parent_display'] = array('default' => '');
+ $options['inherit_yaxis'] = array('default' => '1');
+
+ return $options;
+ }
+
+ /**
+ * Provide the summary for page options in the views UI.
+ *
+ * This output is returned as an array.
+ */
+ function options_summary(&$categories, &$options) {
+ // It is very important to call the parent function here:
+ parent::options_summary($categories, $options);
+
+ $categories['chart'] = array(
+ 'title' => t('Chart settings'),
+ 'column' => 'second',
+ 'build' => array(
+ '#weight' => -10,
+ ),
+ );
+
+ $parent_title = NULL;
+ $parent_display = $this->get_option('parent_display');
+ if (!empty($this->view->display[$parent_display])) {
+ $parent_title = check_plain($this->view->display[$parent_display]->display_title);
+ }
+ $options['parent_display'] = array(
+ 'category' => 'chart',
+ 'title' => t('Combine with parent chart'),
+ 'value' => $parent_title ? $parent_title : t('None'),
+ );
+ $options['inherit_yaxis'] = array(
+ 'category' => 'chart',
+ 'title' => t('Axis settings'),
+ 'value' => $this->get_option('inherit_yaxis') ? t('Use primary Y-axis') : t('Create secondary axis'),
+ );
+
+ }
+
+ /**
+ * Provide the default form for setting options.
+ */
+ function options_form(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_form($form, $form_state);
+
+ switch ($form_state['section']) {
+ case 'parent_display':
+ $form['#title'] .= t('Parent display');
+
+ // Filter down the list of displays to include only those that use
+ // the chart display style.
+ $display_options = array();
+ foreach ($this->view->display as $display_name => $display) {
+ if ($this->view->display[$display_name]->display_options['style_plugin'] === 'chart' && $display_name !== $this->view->current_display) {
+ $display_options[$display_name] = $this->view->display[$display_name]->display_title;
+ }
+ }
+ $form['parent_display'] = array(
+ '#title' => t('Parent display'),
+ '#type' => 'select',
+ '#options' => $display_options,
+ '#empty_option' => t('- None - '),
+ '#required' => TRUE,
+ '#default_value' => $this->get_option('parent_display'),
+ '#description' => t('Select a parent display onto which this chart will be overlaid. Only other displays using a "Chart" format are included here. This option may be used to create charts with several series of data or to create combination charts.'),
+ );
+ break;
+ case 'inherit_yaxis':
+ $form['#title'] .= t('Axis settings');
+ $form['inherit_yaxis'] = array(
+ '#title' => t('Y-Axis settings'),
+ '#type' => 'radios',
+ '#options' => array(
+ 1 => t('Inherit primary of parent display'),
+ 0 => t('Create a secondary axis'),
+ ),
+ '#default_value' => $this->get_option('inherit_yaxis'),
+ '#description' => t('In most charts, the X and Y axis from the parent display are both shared with each attached child chart. However, if this chart is going to use a different unit of measurement, a secondary axis may be added on the opposite side of the normal Y-axis.'),
+ );
+ break;
+
+ }
+ }
+
+ /**
+ * Perform any necessary changes to the form values prior to storage.
+ * There is no need for this function to actually store the data.
+ */
+ function options_submit(&$form, &$form_state) {
+ // It is very important to call the parent function here:
+ parent::options_submit($form, $form_state);
+ switch ($form_state['section']) {
+ case 'parent_display':
+ case 'inherit_yaxis':
+ $this->set_option($form_state['section'], $form_state['values'][$form_state['section']]);
+ break;
+ }
+ }
+}
+
diff --git a/views/charts_plugin_style_chart.inc b/views/charts_plugin_style_chart.inc
index 6775876..cf27d28 100644
--- a/views/charts_plugin_style_chart.inc
+++ b/views/charts_plugin_style_chart.inc
@@ -1,7 +1,7 @@
<?php
/**
* @file
- * Contains the chart style plugin.
+ * Contains the Chart style (format) plugin (similar to Table, HTML List, etc.)
*/
/**
@@ -24,6 +24,12 @@ class charts_plugin_style_chart extends views_plugin_style {
$options[$default_key]['default'] = $default_value;
}
+ // Remove the default setting for chart type so it can be inherited if this
+ // is a chart extension type.
+ if ($this->plugin_name === 'chart_extension') {
+ $options['type']['default'] = NULL;
+ }
+
return $options;
}
@@ -44,6 +50,27 @@ class charts_plugin_style_chart extends views_plugin_style {
module_load_include('inc', 'charts', 'includes/charts.pages');
$field_options = $this->display->handler->get_field_labels();
$form = charts_settings_form($form, $this->options, $field_options, array('style_options'));
+
+ // Reduce the options if this is a chart extension.
+ if ($parent_display = $this->get_parent_chart_display()) {
+ $parent_chart_type = chart_get_type($parent_display->display_options['style_options']['type']);
+ if (empty($form['type']['#default_value'])) {
+ $form['type']['#default_value'] = $parent_display->display_options['style_options']['type'];
+ }
+
+ $form['type']['#description'] = empty($form['type']['#description']) ? '' : $form['type']['#description'] . ' ';
+ $form['type']['#description'] .= t('This chart will be combined with the parent display "@display_title", which is a "@type" chart. Not all chart types may be combined. Selecting a different chart type than the parent may cause errors.', array('@display_title' => $parent_display->display_title, '@type' => $parent_chart_type['label']));
+ $form['fields']['label_field']['#disabled'] = TRUE;
+ $form['display']['#access'] = FALSE;
+ $form['xaxis']['#access'] = FALSE;
+ if ($this->display->handler->options['inherit_yaxis']) {
+ $form['yaxis']['#access'] = FALSE;
+ }
+ else {
+ $form['yaxis']['#title'] = t('Secondary axis');
+ $form['yaxis']['#attributes']['class'] = array();
+ }
+ }
}
/**
@@ -68,7 +95,12 @@ class charts_plugin_style_chart extends views_plugin_style {
return;
}
- if (count($field_handlers)) {
+ // Make sure that all chart extensions first have a parent chart selected.
+ if ($this->plugin_name === 'chart_extension' && $this->get_parent_chart_display() === FALSE) {
+ $errors[] = t('This chart add-on must have a parent chart selected under the chart settings.');
+ }
+ // Make sure that at least one data column has been selected.
+ elseif (count($field_handlers)) {
$data_field_key = !empty($this->options['data_fields']) ? current($this->options['data_fields']) : NULL;
if (empty($data_field_key)) {
$errors[] = t('At least one data field must be selected in the chart configuration before this chart may be shown');
@@ -88,14 +120,17 @@ class charts_plugin_style_chart extends views_plugin_style {
* Define and display a chart from the grouped values.
*/
function render() {
- // Calculate the labels field alias.
$field_handlers = $this->display->handler->get_handlers('field');
+
+ // Calculate the labels field alias.
$label_field = FALSE;
$label_field_key = NULL;
if ($this->options['label_field'] && array_key_exists($this->options['label_field'], $field_handlers)) {
$label_field = $field_handlers[$this->options['label_field']];
$label_field_key = $this->options['label_field'];
}
+
+ // Assemble the fields to be used to provide data access.
$data_field_options = array_filter($this->options['data_fields']);
$data_fields = array();
foreach ($data_field_options as $field_key) {
@@ -145,7 +180,7 @@ class charts_plugin_style_chart extends views_plugin_style {
$chart['#legend_title'] = $label_field->options['label'];
}
- $chart['series'] = array(
+ $chart[$this->view->current_display . '_series'] = array(
'#type' => 'chart_data',
'#data' => $data,
'#title' => $data_field->options['label'],
@@ -165,7 +200,7 @@ class charts_plugin_style_chart extends views_plugin_style {
'#min' => $this->options['yaxis_min'],
);
foreach ($data_fields as $field_key => $field_handler) {
- $chart[$field_key] = array(
+ $chart[$this->view->current_display . '__' . $field_key] = array(
'#type' => 'chart_data',
'#data' => array(),
'#color' => isset($this->options['field_colors'][$field_key]) ? $this->options['field_colors'][$field_key] : NULL,
@@ -182,13 +217,101 @@ class charts_plugin_style_chart extends views_plugin_style {
$chart['xaxis']['#labels'][] = $renders[$row_number][$label_field_key];
}
foreach ($data_fields as $field_key => $field_handler) {
- $chart[$field_key]['#data'][] = (float) $renders[$row_number][$field_key];
+ $chart[$this->view->current_display . '__' . $field_key]['#data'][] = (float) $renders[$row_number][$field_key];
+ }
+ }
+ }
+
+ // Check if this display has any children charts that should be applied
+ // on top of it.
+ $parent_display_id = $this->view->current_display;
+ $children_displays = $this->get_children_chart_displays();
+ foreach ($children_displays as $child_display_id => $child_display) {
+ // If the user doesn't have access to the child display, skip.
+ if (!$this->view->access($child_display_id)) {
+ continue;
+ }
+
+ // Generate the subchart by executing the child display. We load a fresh
+ // view here to avoid collisions in shifting the current display while in
+ // a display.
+ $subview = $this->view->clone_view();
+ $subview->set_display($child_display_id);
+
+ // Copy the settings for our axes over to the child view.
+ foreach ($this->options as $option_name => $option_value) {
+ if (strpos($option_name, 'yaxis') === 0 && $child_display->display_options['inherit_yaxis']) {
+ $subview->display_handler->options['style_options'][$option_name] = $option_value;
+ }
+ elseif (strpos($option_name, 'xaxis') === 0) {
+ $subview->display_handler->options['style_options'][$option_name] = $option_value;
+ }
+ }
+
+ // Execute the subview and get the result.
+ $subview->pre_execute();
+ $subview->execute();
+ $subchart = $subview->style_plugin->render($subview->result);
+ $subview->post_execute();
+ unset($subview);
+
+ // Create a secondary axis if needed.
+ if (!$child_display->display_options['inherit_yaxis'] && isset($subchart['yaxis'])) {
+ $chart['secondary_yaxis'] = $subchart['yaxis'];
+ $chart['secondary_yaxis']['#opposite'] = TRUE;
+ }
+
+ // Merge in the child chart data.
+ foreach (element_children($subchart) as $key) {
+ if ($subchart[$key]['#type'] === 'chart_data') {
+ $chart[$key] = $subchart[$key];
+ // If the subchart is a different type than the parent chart, set
+ // the #chart_type property on the individual chart data elements.
+ if ($chart[$key]['#chart_type'] !== $chart['#chart_type']) {
+ $chart[$key]['#chart_type'] = $subchart['#chart_type'];
+ }
+ if (!$child_display->display_options['inherit_yaxis']) {
+ $chart[$key]['#target_axis'] = 'secondary_yaxis';
+ }
}
}
}
// Print the chart.
- return drupal_render($chart);
+ return $chart;
+ }
+
+ /**
+ * Utility function to check if this chart has a parent display.
+ */
+ function get_parent_chart_display() {
+ $parent_display = FALSE;
+ if ($this->plugin_name === 'chart_extension' && $this->display && $this->display->handler->options['parent_display']) {
+ $parent_display_name = $this->display->handler->options['parent_display'];
+ if (isset($this->view->display[$parent_display_name])) {
+ $parent_display = $this->view->display[$parent_display_name];
+ }
+ }
+ // Ensure the parent is a chart.
+ if ($parent_display && $parent_display->display_options['style_plugin'] !== 'chart') {
+ $parent_display = FALSE;
+ }
+ return $parent_display;
}
+ /**
+ * Utility function to check if this chart has children displays.
+ */
+ function get_children_chart_displays() {
+ $children_displays = array();
+ foreach ($this->view->display as $display_name => $display) {
+ if ($display->display_plugin === 'chart' && $display->display_options['parent_display']) {
+ $parent_display_name = $display->display_options['parent_display'];
+ if ($parent_display_name === $this->view->current_display) {
+ $children_displays[$display_name] = $this->view->display[$display_name];
+ }
+ }
+ }
+ return $children_displays;
+ }
}