* @author Jimmy Berry ("boombatower", http://drupal.org/user/214218) * @package Chart */ /* * Misc */ define('CHART_URI', '//chart.googleapis.com/chart'); /* * Chart types. */ define('CHART_TYPE_LINE', 'lc'); define('CHART_TYPE_LINE_NO_AXIS', 'ls'); define('CHART_TYPE_LINE_XY', 'lxy'); define('CHART_TYPE_BAR_H', 'bhs'); define('CHART_TYPE_BAR_V', 'bvs'); define('CHART_TYPE_BAR_H_GROUPED', 'bhg'); define('CHART_TYPE_BAR_V_GROUPED', 'bvg'); define('CHART_TYPE_PIE', 'p'); define('CHART_TYPE_PIE_3D', 'p3'); define('CHART_TYPE_PIE_CONCENTRIC', 'pc'); define('CHART_TYPE_VENN', 'v'); define('CHART_TYPE_SCATTER', 's'); define('CHART_TYPE_MAP', 't'); define('CHART_TYPE_RADAR', 'r'); define('CHART_TYPE_RADAR_FILLED', 'rs'); define('CHART_TYPE_GMETER', 'gom'); define('CHART_TYPE_QR', 'qr'); /* * Marker types. */ define('CHART_MARKER_ARROW', 'a'); define('CHART_MARKER_CROSS', 'c'); define('CHART_MARKER_DIAMOND', 'd'); define('CHART_MARKER_CIRCLE', 'o'); define('CHART_MARKER_SQUARE', 's'); define('CHART_MARKER_VERTICAL_LINE_X', 'v'); define('CHART_MARKER_VERTICAL_LINE_TOP', 'V'); define('CHART_MARKER_HORIZONTAL_LINE', 'h'); define('CHART_MARKER_X', 'x'); /* * Axis. */ define('CHART_AXIS_X_BOTTOM', 'x'); define('CHART_AXIS_X_TOP', 't'); define('CHART_AXIS_Y_LEFT', 'y'); define('CHART_AXIS_Y_RIGHT', 'r'); /** * Alignment. */ define('CHART_ALIGN_LEFT', -1); define('CHART_ALIGN_CENTER', 0); define('CHART_ALIGN_RIGHT', 1); /** * Legend Position */ define('CHART_LEGEND_BOTTOM', 'b'); define('CHART_LEGEND_TOP', 't'); define('CHART_LEGEND_BOTTOM_VERTICAL', 'bv'); define('CHART_LEGEND_TOP_VERTICAL', 'tv'); define('CHART_LEGEND_RIGHT', 'r'); define('CHART_LEGEND_LEFT', 'l'); /*----------------------------------------------------------------- * Hook Implementations *------------------------------------------------------------------*/ /** * Implements hook_permission(). */ function chart_permission() { return array( 'administer chart' => array( 'title' => t('Administer chart'), 'description' => t('Modify chart settings.'), ), ); } /** * Implements hook_menu(). */ function chart_menu() { $items = array(); $items['admin/config/media/chart'] = array( 'title' => 'Chart', 'description' => 'Modify chart settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('chart_settings'), 'access arguments' => array('administer chart'), ); return $items; } /** * Implements hook_theme(). */ function chart_theme($existing, $type, $theme, $path) { return array( 'chart' => array( 'render element' => 'chart', ), ); } /*----------------------------------------------------------------- * Public API *------------------------------------------------------------------*/ /** * Renders a chart element. * * @param $variables * An associative array containing: * - chart: An associative array definining a chart. */ function theme_chart($variables) { $chart = $variables['chart']; if (chart_build($chart)) { $chart['#attributes']['id'] = 'chart-' . $chart['#chart_id']; $chart['#attributes']['class'][] = 'chart'; return theme('image', array('path' => chart_url($chart), 'attributes' => $chart['#attributes'], 'alt' => $chart['#title'] ? $chart['#title'] : t('Chart'))); } return ''; } /** * Returns the chart URL. * * @param array $chart * * @return mixed * - Success: Chart image markup * - Failure: FALSE */ function chart_url($chart) { if ($data = chart_build($chart)) { return url(CHART_URI, array('query' => $data, 'external' => TRUE)); } return FALSE; } /** * Copies rendered chart image. * * @param array $chart * Chart API structure * * @param string $name * (optional) Filename WITHOUT extension. * when NULL #chart_id is used. * * @param string $dest * (optional) A string containing the path to verify. If this value is * omitted, Drupal's 'files/charts' directory will be used. * * @param string $replace * (optional) Replace behavior when the destination file already exists. * - FILE_EXISTS_REPLACE: Replace the existing file * - FILE_EXISTS_RENAME: Appends _{incrementing number} until the filename is unique * - FILE_EXISTS_ERROR: Do nothing and return FALSE. * * @return bool/string * Returns FALSE on failure, and file path on success. */ function chart_copy($chart, $name = NULL, $dest = 'charts', $replace = FILE_EXISTS_REPLACE) { if (!chart_build($chart)) { return FALSE; } if ($dest = file_create_path($dest)) { if (file_prepare_directory($dest, FILE_CREATE_DIRECTORY)) { // Defaults $name = is_null($name) ? $chart['#chart_id'] : $name; // Generate temp image $tempname = tempnam(file_directory_temp(), 'tmp_'); $filename = file_create_path($dest) . '/' . $name . '.png'; $png = imagecreatefrompng(chart_url($chart)); $fh = fopen($tempname, 'w+'); imagepng($png, $tempname); // Copy temp image to new location if (!file_copy($tempname, $filename, $replace)) { _chart_error(t('Failed to copy chart image.')); return FALSE; } // Remove temp image fclose($fh); } return $filename; } return FALSE; } /** * Build chart query data. * * @param $chart * An associative array defining a chart which may contain the following * keys. * - #chart_id: Unique chart indentifier. * - #type: (cht) The chart type, see chart_types(). * - #data: An array of data. * - #title: (chtt) (Optional) The chart title. * - #size: (chs) (Optional) An associative array containing keys: #width and * #height. * - #legends: (chdl) (Optional) An array of legends. * - #legend_position: (chdlp) (Optional) ... * - #labels: (chl) (Optional) An array of labels. * - #adjust_resolution: (Optional) TRUE if the resolution of data should be * automatically adjusted, an associative array containing #adjust which * should be equal to previous description and #max to adjust data by. * - #line_styles: (chls) (Optional) An array of line styles. * - #grid_lines: (chg) (Optional) An array of grid line information. * - #shape_markers: (chm) (Optional) A single or an array of shape markers. * - #data_colors: (chco) (Optional) An array of data colors. * - #chart_fill: (chf) (Optional) A single or an array of chart fill colors. * - #mixed_axis_labels: (chxt) (Optional) An array of mixed axis labels. * - #mixed_axis_label_styles: (chxs) (Optional) An array of mixed axis label * styles. * - #bar_size: (chbh) (Optional) 'a' for automatic, 'r' for relative, or an array using keys: #size and #spacing. * - #countries: (chld) (Optional) An array of countries. * - #georange: (chtm) (Optional) The geographical scope. * * @return mixed * Associative array of query data, otherwise FALSE. * @see _chart_append() */ function chart_build($chart) { $charts = &drupal_static(__FUNCTION__, array()); if (empty($chart['#chart_id'])) { trigger_error('Charts must provide a #chart_id.', E_USER_ERROR); return FALSE; } // Hide charts with no data. if (empty($chart['#data'])) { return FALSE; } // If the chart has not been built then proceed to build it. if (!isset($charts[$chart['#chart_id']])) { // Merge optional parameters defaults. $chart += array( '#title' => '', '#size' => '', '#legends' => array(), '#legend_position' => '', '#labels' => array(), '#adjust_resolution' => FALSE, '#line_styles' => array(), '#grid_lines' => array(), '#shape_markers' => array(), '#data_colors' => array(), '#chart_fill' => array(), '#mixed_axis_labels' => array(), '#mixed_axis_label_styles' => array(), '#bar_size' => '', '#countries' => array(), '#georange' => '', ); // Allow modules to alter a chart after defaults have been added. drupal_alter('chart', $chart); // Adjust resolution if option is enabled. if (!empty($chart['#adjust_resolution'])) { if ($chart['#adjust_resolution'] === TRUE) { _chart_adjust_resolution($chart['#chart_id'], $chart['#data']); } elseif ($chart['#adjust_resolution']['#adjust'] == TRUE) { _chart_adjust_resolution($chart['#chart_id'], $chart['#data'], $chart['#adjust_resolution']['#max']); } } $data = array(); $data['chd'] = 't:' . _chart_encode_data($chart['#data']); _chart_append('cht', $chart['#type'], $data); _chart_append('chs', $chart['#size'], $data); _chart_append('chtt', $chart['#title'], $data); _chart_append('chl', $chart['#labels'], $data); _chart_append('chdl', $chart['#legends'], $data); _chart_append('chdlp', $chart['#legend_position'], $data); _chart_append('chls', $chart['#line_styles'], $data); _chart_append('chg', $chart['#grid_lines'], $data); _chart_append('chm', $chart['#shape_markers'], $data); _chart_append('chco', $chart['#data_colors'], $data); _chart_append('chf', $chart['#chart_fill'], $data); _chart_append('chxt', $chart['#mixed_axis_labels'], $data); _chart_append('chxs', $chart['#mixed_axis_label_styles'], $data); _chart_append('chbh', $chart['#bar_size'], $data); _chart_append('chld', $chart['#countries'], $data); _chart_append('chtm', $chart['#georange'], $data); $charts[$chart['#chart_id']] = $data; } return $charts[$chart['#chart_id']]; } /*----------------------------------------------------------------- * Page Callbacks *------------------------------------------------------------------*/ /** * Settings form page callback handler. */ function chart_settings($form, &$form_state) { $form['overrides'] = array( '#type' => 'fieldset', '#title' => t('Overrides'), '#collapsible' => TRUE, '#collapsed' => FALSE, ); // @todo: overrides global / per theme $form['overrides']['chart_global_bg'] = array( '#type' => 'textfield', '#title' => t('Global Background Color'), '#description' => t('Specify a global background color for all charts. When set this will take precedence over any custom value which is useful for highly themed sites.'), '#default_value' => variable_get('chart_global_bg', ''), ); $form['overrides']['chart_max_width'] = array( '#type' => 'textfield', '#title' => t('Max Width'), '#description' => t('Max chart width.'), '#default_value' => variable_get('chart_max_width', ''), ); $form['overrides']['chart_max_height'] = array( '#type' => 'textfield', '#title' => t('Max Height'), '#description' => t('Max chart height.'), '#default_value' => variable_get('chart_max_height', ''), ); return system_settings_form($form); } /** * Settings page validation. */ function chart_settings_validate($form, &$form_state) { if (!empty($form_state['values']['chart_global_bg']) && !preg_match('/[a-fA-F0-9]{6}/is', $form_state['values']['chart_global_bg'])) { form_set_error('chart_global_bg', t('Invalid color. Formatted as RRGGBB with no pound sign.')); } if (!empty($form_state['values']['chart_max_width']) && !is_numeric($form_state['values']['chart_max_width'])) { form_set_error('chart_max_width', t('Width must be an integer.')); } if (!empty($form_state['values']['chart_max_height']) && !is_numeric($form_state['values']['chart_max_height'])) { form_set_error('chart_max_height', t('Height must be an integer.')); } } /*----------------------------------------------------------------- * Helpers *------------------------------------------------------------------*/ /** * Encode an array of data. * * Missing data placeholder 'NULL' is replaced * the appropriate placeholder. * * @return string */ function _chart_encode_data($data) { $output = ''; if (count($data)) { foreach ($data as $k => $v) { if (is_array($v) || is_object($v)) { $output .= '|'; $output .= _chart_encode_data($v); } else { if (is_numeric($v)) { $output .= $v . ','; } else { $output .= '-1,'; } } } } return trim($output, '|,'); } /** * Adjusts chart data transforming values so that they may * be represented properly within the given resolution * for the selected encoding type. */ function _chart_adjust_resolution($chart_id, &$data, $max_value = NULL) { $max = &drupal_static(__FUNCTION__, array()); if (count($data)) { // Set max data value if (!isset($max[$chart_id])) { $max[$chart_id] = isset($max_value) ? $max_value : _chart_get_max($data); $max[$chart_id] += $max[$chart_id] * 0.05; // Add a margin. } // Encoding resolution $resoluton = 100; // When the max is larger than the resolution // we need to scale down the values if ($max[$chart_id] > $resoluton) { $divider = $max[$chart_id] / $resoluton; } elseif ($max[$chart_id] > 0) { $multiplier = $resoluton / $max[$chart_id]; } foreach ($data as $k => $v) { if (is_array($v)) { _chart_adjust_resolution($chart_id, $data[$k]); } else { if (is_numeric($v)) { // Adjust values if ($v >= $max) { $data[$k] = $resoluton; } else { // Multiply or divide data values to adjust them to the resolution if (isset($divider)) { $data[$k] = floor($v / $divider); } elseif (isset($multiplier)) { $data[$k] = floor($v * $multiplier); } else { $data[$k] = $v; } } } } } } } /** * When the value passed is valid append a chart API * attribute and parsed values to $data. */ function _chart_append($attr, $value, &$data) { // Size and fill contain defaults, all other attributes must be set if (!$value && $attr != 'chs' && $attr != 'chf') { return; } switch ($attr) { // Type case 'cht': $data[$attr] = $value; break; // Size case 'chs': if (_chart_is_valid_size($value)) { $width = $value['#width']; $height = $value['#height']; } else { $width = 300; $height = 150; } _chart_override_aspect($width, $height); $data[$attr] = $width . 'x' . $height; break; // Labels case 'chl': $data[$attr] = implode('|', $value); break; // Color case 'chco': $data[$attr] = implode(',', $value); break; // Chart and background fill case 'chf': if (variable_get('chart_global_bg', FALSE) || !isset($value)) { $data[$attr] = implode(',', chart_fill('bg', trim(variable_get('chart_global_bg', 'FFFFFF'), '#'))); } else { $data[$attr] = implode(',', $value); } break; // Chart title case 'chtt': if (is_array($value)) { $data[$attr] = $value['#title']; if (!isset($data['chts'])) { $data['chts'] = ''; } $data['chts'] .= isset($value['#color']) ? $value['#color'] : '000000'; $data['chts'] .= ','; $data['chts'] .= isset($value['#size']) ? $value['#size'] : 14; } else { $data[$attr] = $value; } break; default: // Legends case 'chdl': $data[$attr] = implode('|', $value); break; // Legend's position case 'chdlp': $data[$attr] = $value; break; // Line styles case 'chls': $styles = array(); if (count($value)) { foreach ($value as $k => $v) { $tmp_style = array(); if (!is_array($v)) { // Style parameters $tmp_style[] = $v; } else { // Array of styles $styles[] = implode(',', $v); } if (count($tmp_style)) { $styles[] = implode(',', $tmp_style); } } } if (count($styles)) { $data[$attr] = implode('|', $styles); } break; // Grid lines case 'chg': $data[$attr] = implode(',', $value); break; // Shape markers case 'chm': if (count($value)) { $markers = array(); foreach ($value as $marker) { $markers[] = implode(',', $marker); } $data[$attr] = implode('|', $markers); } else { $data[$attr] = implode(',', $value); } break; // Bar chart bar sizing case 'chbh': if (!isset($data[$attr])) { $data[$attr] = ''; } //If not array, use as is. if (!is_array($value)) { $data[$attr] .= $value; } else { $data[$attr] .= implode(',', array($value['#size'], $value['#spacing'])); } break; // Mixed axis positions, labels and styles // @todo: refactor case 'chxt': $index = 0; $positions = array(); $types = array(); $regular = array(); // Seperate regular labels and generate range labels if (count($value)) { foreach ($value as $axis => $indices) { if (count($indices)) { ksort($indices); foreach ($indices as $i => $labels) { if (count($labels)) { foreach ($labels as $j => $label) { // Regular if (isset($label['#label'])) { // Array of labels if (is_array($label['#label'])) { foreach ($label['#label'] as $l) { $regular[$axis][$i][] = is_array($l) ? $l : array('#label' => $l, '#position' => NULL); } } else { $regular[$axis][$i][] = $label; } } // Range elseif (isset($label['#start']) && isset($label['#end'])) { if (!isset($data['chxr'])) { $data['chxr'] = ''; } $data['chxr'] .= $index . ',' . $label['#start'] . ',' . $label['#end'] . '|'; $types[] = $axis; $index++; } } } } } } } // Generate regular labels if (count($regular)) { foreach ($regular as $axis => $indices) { if (count($indices)) { if (!isset($data['chxl'])) { $data['chxl'] = ''; } foreach ($indices as $i => $labels) { $types[] = $axis; $data['chxl'] .= $index . ':'; if (count($labels)) { foreach ($labels as $j => $label) { $data['chxl'] .= '|' . $label['#label']; $positions[$index]['data'][] = isset($label['#position']) ? $label['#position'] : 0; if ($label['#position']) { $positions[$index]['set'] = TRUE; } } } $data['chxl'] .= '|'; $index++; } } } } // Generate positions if (count($positions)) { foreach ($positions as $i => $position) { if (isset($position['set']) && $position['set'] == TRUE) { if (!isset($data['chxp'])) { $data['chxp'] = ''; } $data['chxp'] .= $i . ',' . implode(',', $position['data']) . '|'; } } } $data['chxt'] = implode(',', $types); $data['chxl'] = isset($data['chxl']) ? rtrim($data['chxl'], '|') : ''; $data['chxr'] = isset($data['chxr']) ? rtrim($data['chxr'], '|') : ''; $data['chxp'] = isset($data['chxp']) ? rtrim($data['chxp'], '|') : ''; break; // Mixed axis label styles case 'chxs': if (count($value)) { $styles = array(); foreach ($value as $i => $style) { $styles[] = implode(',', $style); } $data[$attr] = implode('|', $styles); } break; // Geographical Scope case 'chtm': $data[$attr] = $value; break; // Country List case 'chld': $data[$attr] = implode('', $value); break; } } /** * Return the max value of a single level array. */ function _chart_get_max($array) { rsort($array, SORT_NUMERIC); $max = is_array($array[0]) ? 1 : $array[0]; if (count($array)) { foreach ($array as $k => $v) { if (is_array($v)) { rsort($v, SORT_NUMERIC); if ($v[0] > $max) { $max = $v[0]; } } } } return $max; } /** * Override the default chart aspect ratio. */ function _chart_override_aspect(&$width, &$height) { $decrement = 20; $max_width = variable_get('chart_max_width', FALSE); $max_height = variable_get('chart_max_height', FALSE); if (!$max_width && !$max_height) { return; } // Width if ($max_width) { while ($width > $max_width) { $width -= $decrement; $height -= $decrement; } } // Height if ($max_height) { while ($height > $max_height) { $width -= $decrement; $height -= $decrement; } } } /** * Admin error. */ function _chart_error($message, $admin = TRUE) { if ($admin) { if (user_access('administer chart')) { drupal_set_message(t('Chart API error: ') . $message, 'error'); } } else { drupal_set_message($message, 'error'); } } /** * Check if chart data is below size limit. */ function _chart_is_valid_size($size) { if (!empty($value)) { return FALSE; } if (!is_numeric($size['#width']) && !is_numeric($size['#height'])) { return FALSE; } if (($size['#width'] * $size['#height']) >= 300000) { watchdog('chart', '#width X #height of chart is greater than or equal to 300,000 pixels, chart size set to default.'); return FALSE; } return TRUE; } /** * Get the available color schemes. * * @see hook_chart_color_schemes() * @see hook_chart_color_schemes_alter() */ function chart_color_schemes() { $schemes = &drupal_static(__FUNCTION__); if (!$schemes) { // Allow modules to define color schemes. $schemes = module_invoke_all('chart_color_schemes'); // Provide the default color scheme. $schemes['default'] = array( 'FF8000', 'FFC080', 'FFDFBF', 'FFC080', 'FFCC00', 'FFE500', 'FFF9BF', '78c0e9', '179ce8', '30769e', 'c8e9fc', 'ecf8ff', '00ccff', '4086AA', '91C3DC', '87907D', 'AAB6A2', '555555', '666666', '21B6A8', '177F75', 'B6212D', '7F171F', 'B67721', '7F5417', ); // Allow modules to alter color schemes. drupal_alter('chart_color_schemes', $schemes); } return $schemes; } /*----------------------------------------------------------------- * Label Utils *------------------------------------------------------------------*/ /** * Title * * @param string $title * * @param string $color * * @param int $size * * @return array */ function chart_title($title, $color = '000000', $size = 14) { return array( '#title' => $title, '#color' => $color, '#size' => $size, ); } /** * Create a mixed axis label. * * Labels must be nested by axis and index: * @code * $chart['#mixed_axis_labels'][CHART_AXIS_X_BOTTOM][0][] = chart_mixed_axis_label(t('Monday')); * $chart['#mixed_axis_labels'][CHART_AXIS_X_BOTTOM][0][] = chart_mixed_axis_label(t('Tuesday')); * $chart['#mixed_axis_labels'][CHART_AXIS_X_BOTTOM][0][] = chart_mixed_axis_label(t('Wednesday')); * $chart['#mixed_axis_labels'][CHART_AXIS_Y_LEFT][1][] = chart_mixed_axis_range_label(0, 50); * $chart['#mixed_axis_labels'][CHART_AXIS_Y_LEFT][2][] = chart_mixed_axis_label(t('Min')); * $chart['#mixed_axis_labels'][CHART_AXIS_Y_LEFT][2][] = chart_mixed_axis_label(t('Max')); * @endcode * * @param mixed $label * - string: A single label * - array: An array of label strings, or an assoc array containing #label and #position * * @param int $position * (optional) An integer between 0 - 100 representing where the label should appear along the axis. * 0 representing bottom and left, 100 representing top and right. When one label within a given set * is given a position, the remaining labels in the set must have a position. * * @return array */ function chart_mixed_axis_label($label, $position = NULL) { return array( '#label' => $label, '#position' => $position, ); } /** * Create a mixed axis range label. * * Labels must be nested by axis and index: * @code * $chart['#mixed_axis_labels'][CHART_AXIS_X_BOTTOM][0][] = chart_mixed_axis_label(t('Monday')); * $chart['#mixed_axis_labels'][CHART_AXIS_X_BOTTOM][0][] = chart_mixed_axis_label(t('Tuesday')); * $chart['#mixed_axis_labels'][CHART_AXIS_X_BOTTOM][0][] = chart_mixed_axis_label(t('Wednesday')); * $chart['#mixed_axis_labels'][CHART_AXIS_Y_LEFT][1][] = chart_mixed_axis_range_label(0, 50); * $chart['#mixed_axis_labels'][CHART_AXIS_Y_LEFT][2][] = chart_mixed_axis_label(t('Min')); * $chart['#mixed_axis_labels'][CHART_AXIS_Y_LEFT][2][] = chart_mixed_axis_label(t('Max')); * @endcode * * @param string $start * * @param string $end * * @return array */ function chart_mixed_axis_range_label($start, $end) { return array( '#start' => $start, '#end' => $end, ); } /** * Create a mixed axis for a corresponding label set index. * * @param int $index * * @param string $color * * @param int $font_size * * @param int $alignment * - CHART_ALIGN_LEFT * - CHART_ALIGN_CENTER * - CHART_ALIGN_RIGHT * * @return array */ function chart_mixed_axis_label_style($index, $color, $font_size = 12, $alignment = CHART_ALIGN_CENTER) { return array( '#index' => $index, '#color' => $color, '#font_size' => $font_size, '#alignment' => $alignment, ); } /*----------------------------------------------------------------- * Style Utils *------------------------------------------------------------------*/ /** * Chart size * * @return array */ function chart_size($width, $height) { return array( '#width' => $width, '#height' => $height ); } /** * Data Colors * * @param array $colors * * @return array */ function chart_data_colors($colors) { return $colors; } /** * Line Style * * @param int $line_thickness * * @param int $segment_length * * @param int $blank_segment_length * * @return array */ function chart_line_style($line_thickness = 1, $segment_length = 1, $blank_segment_length = 0) { return array( '#line_thickness' => $line_thickness, '#segment_length' => $segment_length, '#blank_segment_length' => $blank_segment_length, ); } /** * Grid Lines * * @param int $x_step * Space in pixels in which to step the horizontal lines. * * @param int $y_step * Space in pixels in which to step the virtical lines. * * @param int $segment_length * (optional) Visibile segment length in pixels. * * @param int $blank_segment_length * (optional) Blank segment length in pixels. * * @return array */ function chart_grid_lines($x_step, $y_step, $segment_length = 1, $blank_segment_length = 3) { return array( '#x_step' => $x_step, '#y_step' => $y_step, '#segment_length' => $segment_length, '#blank_segment_length' => $blank_segment_length, ); } /** * Shape Marker * * @param int $index * (optional) The index of the line on which to draw the marker. * * @param float $point * (optional) Floating point value that specifies on which data point * the marker will be drawn. * * @param string $type * - CHART_MARKER_ARROW * - CHART_MARKER_CROSS * - CHART_MARKER_DIAMOND * - CHART_MARKER_CIRCLE * - CHART_MARKER_SQUARE * - CHART_MARKER_VERTICAL_LINE_X * - CHART_MARKER_VERTICAL_LINE_TOP * - CHART_MARKER_HORIZONTAL_LINE * - CHART_MARKER_X * * @param int $size * (optional) Marker size in pixels. * * @param string $color * * @return array */ function chart_shape_marker($index = 0, $point = 0, $type = 'o', $size = 20, $color = '000000') { return array( '#type' => $type, '#color' => $color, '#index' => $index, '#point' => $point, '#size' => $size, ); } /** * Range Marker * * @param int $start * * @param int $end * * @param bool $virtical * * @param string $color * * @return array */ function chart_range_marker($start, $end, $virtical = TRUE, $color = '000000') { return array( '#start' => $start, '#end' => $end, '#virtical' => $virtical, '#color' => $color, ); } /** * Bar chart bar sizing. * * @param int $size * (optional) Height or width of the bar. * * @param int $spacing * (optional) Pixel spacing between bars. * * @return array */ function chart_bar_size($size = 40, $spacing = 20) { return array( '#size' => $size, '#spacing' => $spacing, ); } /** * Solid Fill * * @param int $type * (optional) 'bg' or 'c' * * @param string $color * * @return array */ function chart_fill($type = 'c', $color = '000000') { return array( '#type' => $type, '#fill_type' => 's', '#color' => $color, ); } /** * Linear Gradient * * @param int $type * (optional) 'bg' or 'c' * * @param int $color * * @param int $offset * (optional) Cpecify at what point the color is pure where: 0 specifies the right-most * chart position and 1 the left-most. * * @return array */ function chart_linear_gradient($type = 'c', $color = '000000', $offset = 0) { return array( '#type' => $type, '#fill_type' => 'lg', '#color' => $color, ); } /** * Linear Stripes * * @param int $type * (optional) 'bg' or 'c' * * @param int $color * * @param int $angle * (optional) Specifies the angle of the gradient between 0 (horizontal) and 90 (vertical). * * @param float $width * (optional) Must be between 0 and 1 where 1 is the full width of the chart. Stripes are * repeated until the chart is filled. * * @return array */ function chart_linear_stripes($type = 'c', $color = '000000', $angle = 0, $width = 0.25) { return array( '#type' => $type, '#fill_type' => 'ls', '#angle' => $angle, '#color' => $color, '#width' => $width, ); } /*----------------------------------------------------------------- * Color Schemes *------------------------------------------------------------------*/ /** * Supplies a unique color. * * When an assoc array color scheme is provided * $content_id can be used to sync the color to * the data rendered. Otherwise the next available * color in the stack is assigned to $content_id * * @param string $content_id * * @param string $scheme * (optional) Color scheme. * * @return string * hex RGB value */ function chart_unique_color($content_id, $scheme = 'default') { $colors_used = &drupal_static(__FUNCTION__); $colors = chart_color_schemes(); // Revert to default color schema if $schema has not been registered. if (!isset($colors[$scheme])) { $scheme = 'default'; } // Associative color is available if (isset($colors[$scheme][$content_id])) { return $colors[$scheme][$content_id]; } // The content_id has already been mapped elseif (isset($colors_used[$scheme][$content_id])) { return $colors_used[$scheme][$content_id]; } else { // No used colors yet use the first one in the scheme // and map it to the content id if (!is_array($colors_used)) { $colors_used[$scheme][$content_id] = array_shift($colors[$scheme]); return $colors_used[$scheme][$content_id]; } // Get the avilable colors left in the scheme // and map the remaining colors that are used else { $available_colors = array_diff($colors[$scheme], (array) $colors_used[$scheme]); $colors_used[$scheme][$content_id] = array_shift($available_colors); return $colors_used[$scheme][$content_id]; } } return '000000'; } /** * Get a map of chart types to human-readable names. * * @return * An associative array of chart types. */ function chart_types() { return array( CHART_TYPE_LINE => t('Line'), CHART_TYPE_LINE_NO_AXIS => t('Line - no axis'), CHART_TYPE_LINE_XY => t('Line - XY'), CHART_TYPE_BAR_H => t('Bar - Horizontal'), CHART_TYPE_BAR_V => t('Bar - Vertical'), CHART_TYPE_BAR_H_GROUPED => t('Bar - Grouped Horizontal'), CHART_TYPE_BAR_V_GROUPED => t('Bar - Grouped Vertical'), CHART_TYPE_PIE => t('Pie'), CHART_TYPE_PIE_3D => t('Pie - 3D'), CHART_TYPE_PIE_CONCENTRIC => t('Pie - Concentric'), CHART_TYPE_VENN => t('Venn Diagram'), CHART_TYPE_SCATTER => t('Scatter plot'), CHART_TYPE_MAP => t('Map'), CHART_TYPE_RADAR => t('Radar'), CHART_TYPE_RADAR_FILLED => t('Radar - filled'), CHART_TYPE_GMETER => t('Goole-o-meter'), CHART_TYPE_QR => t('QR code'), ); }