'fusioncharts_data_jscallback', 'type' => MENU_CALLBACK, 'access arguments' => array('access content'), ); return $items; } /** * XML generation from data * This function is called by fusioncharts_render and by fusioncharts_data_jscallback initiated by the javascript onclick action. * * @param string $jsid Name to be used for the id attribute in the DOM (used for javascript manipulation) * @param string $callbackfn The name of the function which holds the chart definition or object of the callback data * @param array $args Any further arguments recieved by the function. These arguments will be passed to the callback function. optional * @return the XML data to draw the chart */ function fusioncharts_data() { $args = func_get_args(); $jsid = array_shift($args); $param = array_shift($args); if (is_object($param)) { $callback_data = $param; } else { $callbackfn = strtolower($param); $callback_data = call_user_func($callbackfn .'_fusionchart_callback', $args); } if (isset($callback_data->query)) { $data = fusioncharts_prepare_data_query($callback_data); } else { $data = $callback_data->data; } $data = (array)$data; //populate some colors into attributes if none are set if (!isset($callback_data->attributes['color'])) { $attributes = fusioncharts_settings('fusioncharts-defattr'); $colors = array_values($attributes['color']); foreach ($colors as $color) { if (substr($color, 0, 1) == '#' && strlen($color) == 7) { //remove the # from the color $callback_data->attributes['color'][] = substr($color, 1); } } } $str_xml = "\nsettings as $key => $value) { if ($value === '') { //exclude unset keys } else { $str_xml .= " ". $key ."='". fusioncharts_entities($value) ."'"; } } $str_xml .= ">\n"; //build the data structure switch ($callback_data->chart_type) { case 'Column 3D': case 'Column 2D': case 'Line 2D': case 'Area 2D': case 'Bar 2D': case 'Pie 2D': case 'Pie 3D': case 'Doughnut 2D': case 'Funnel Chart': $str_xml .= _fusioncharts_data_single($data, $callback_data->attributes, $jsid); break; case 'Multi-series Column 2D': case 'Multi-series Column 3D': case 'Multi-series Line 2D': case 'Multi-series Bar 2D': case 'Multi-series Area 2D': case 'Stacked Column 3D': case 'Stacked Column 2D': case 'Stacked Bar 2D': case 'Stacked Area 2D': $str_xml .= _fusioncharts_data_multi($data, $callback_data->attributes, $jsid); break; case 'Multi-series Column 2D + Line - Dual Y Axis': case 'Multi-series Column 3D + Line - Dual Y Axis': $str_xml .= _fusioncharts_data_combination($data, $callback_data->attributes, $jsid); break; case 'Candlestick Chart': break; case 'Gantt Chart': break; } if (isset($callback_data->attributes['trendline'])) { $str_xml .= "\n"; if (is_array($callback_data->attributes['trendline'][0])) { foreach ($callback_data->attributes['trendline'] as $line) { $attrs = ''; foreach ($line as $atrib => $value) { $attrs .= "$atrib='$value'"; } $str_xml .= "\n"; } } else { foreach ($callback_data->attributes['trendline'] as $atrib => $value) { $attrs .= "$atrib='$value' "; } $str_xml .= "\n"; } $str_xml .= "\n"; } $str_xml .= "\n"; $callback_data->xml = $str_xml; return $callback_data; } /** * Menu callback for the javascript onclick action (AJAX) * This function passes values into fusioncharts_data and echos back the result * * @param string $jsid Name to be used for the id attribute in the DOM (used for javascript manipulation) * @param string $callbackfn The name of the function which holds the chart definition * @param args $args Data returned by the onclick action - this can be used to identify which bar was clicked. * @return string the XML data to the AJAX call */ function fusioncharts_data_jscallback() { $args = func_get_args(); $jsid = array_shift($args); $callbackfn = array_shift($args); $callback_data = fusioncharts_data($jsid, $callbackfn, $args); echo $callback_data->xml; } /** * Translate data from a query into an array * * @param object $callback data * @return array data */ function fusioncharts_prepare_data_query($callback_data) { $result = db_query($callback_data->query); if (!$result) { drupal_set_message("No data returned from the query '{$callback_data->query}'"); exit(); } $i=0; switch ($callback_data->chart_type) { case 'Column 3D': case 'Column 2D': case 'Line 2D': case 'Area 2D': case 'Bar 2D': case 'Pie 2D': case 'Pie 3D': case 'Doughnut 2D': case 'Funnel Chart': while ($row = db_fetch_object($result)) { $data[$i][0] = $row->name; $data[$i][1] = $row->value; $i++; } break; case 'Multi-series Column 2D': case 'Multi-series Column 3D': case 'Multi-series Line 2D': case 'Multi-series Bar 2D': case 'Multi-series Area 2D': case 'Stacked Column 3D': case 'Stacked Column 2D': case 'Stacked Bar 2D': case 'Stacked Area 2D': while ($row = db_fetch_object($result)) { $data[$i][0] = $row->category; $data[$i][1] = $row->series; $data[$i][2] = $row->value; $i++; } break; case 'Multi-series Column 2D + Line - Dual Y Axis': case 'Multi-series Column 3D + Line - Dual Y Axis': while ($row = db_fetch_object($result)) { $data[$i][0] = $row->category; $data[$i][1] = $row->series; $data[$i][2] = $row->axis; $data[$i][3] = $row->value; $i++; } break; case 'Candlestick Chart': //probably too complex for a simple query break; case 'Gantt Chart': //probably too complex for a simple query break; } return (array)$data; } /** * Data generation for single series charts * This function is called by fusioncharts_data * * @param array $data Data to be converted to XML * @param array $attributes See api.txt for details * @param string $jsid javascript id * @return string XML */ function _fusioncharts_data_single($data, $attributes = array(), $jsid = NULL) { //validate and reform the data as necessary //for single series charts the validation needed is: // * that the value column is numeric // * that there is a value for each series foreach ($data as $column) { if ($column[0] != '' && is_numeric($column[1])) { $clean_data[] = $column; } elseif ($column[0] != '' && !is_numeric($column[1])) { drupal_set_message(t('Data row @row did not validate. Please check it', array('@row' => $column[0]))); } } //build the xml $c = 0; $str_xml = ''; foreach ($clean_data as $column) { unset($link); if (isset($attributes['callback'])) { $link = "link='JavaScript:fusioncharts_clickbar("$jsid", "{$attributes['callback']}","{$column[0]}")'"; } if (isset($attributes['link'][$column[0]])) { $link = "link='". urlencode($attributes['link'][$column[0]]) ."'"; } if (isset($attributes['color'][$c])) { $color = "color='{$attributes['color'][$c]}'"; } if (isset($attributes['hoverText'][$c])) { $hovertext = "hoverText='". fusioncharts_entities(trim($attributes['hoverText'][$c])). "'"; } if (isset($attributes['alpha'][$column[0]])) { $alpha = "alpha='{$attributes['alpha'][$column[0]]}'"; } if (isset($attributes['showName'][$column[0]])) { $showname = "showName='{$attributes['showName'][$column[0]]}'"; } $str_xml .= "\n"; $c++; } return $str_xml; } /** * Data generation for multi series charts * This function is called by fusioncharts_data * * @param array $data Data to be converted to XML * @param array $attributes See api.txt for details * @param string $jsid javascript id * @return string XML */ function _fusioncharts_data_multi($data, $attributes = array(), $jsid=NULL) { //validate and reform the data as necessary //for multi series charts the validation needed: // * that the value column is numeric // * that there is a value for each series // * that there is the same number of series for each category. foreach ($data as $column) { $series[$column[0]]++; $categories[$column[1]]++; $value[$column[1]][$column[0]] = $column[2]; $value_count++; if (!is_numeric($column[2])) { $numeric_error = TRUE; } } if ((count($series) * count($categories)) != $value_count || $numeric_error == TRUE) { drupal_set_message(t('The data for this chart is incomplete or contains an error. Graph cannot be drawn.'), 'error');//error - some data missing return; } else { //reconstruct the data so its in the right form foreach($series as $s => $d) { foreach($categories as $category => $d) { $clean_data[] = array($s, $category, $value[$category][$s]); } } } unset($series, $categories, $value); //build the xml $c=0; $str_xml = ''; foreach ($clean_data as $column) { $series[] = $column[0]; $categories[] = $column[1]; $value[] = $column[2]; } $str_xml .= "\n"; $category = array_unique($categories); if (end($category) == '') { array_pop($category); } $category_count = count($category); $total_results = count($value); foreach ($category as $this_category) { $str_xml .= "\n"; } $str_xml .= "\n"; for ($i=0; $i<$total_results; $i+=$category_count) { if ($series[$i] != '') { if (isset($attributes['color'][$c])) { $color = "color='{$attributes['color'][$c]}'"; } $str_xml .= "\n"; for ($j=$i; $j<($i+$category_count); $j++) { unset($link); if ($value[$j] != '') { if (isset($attributes['callback'])) { $link = "link='JavaScript:fusioncharts_clickbar("$jsid", "{$attributes['callback']}", "{$series[$j]}", "{$categories[$j]}")'"; } if (is_string($attributes['link'][$series[$j]])) { $link = "link='". urlencode($attributes['link'][$series[$j]]) ."'"; } elseif (isset($attributes['link'][$series[$j]][$categories[$j]])) { $link = "link='". urlencode($attributes['link'][$series[$j]][$categories[$j]]) ."'"; } $str_xml .= "\n"; } } $str_xml .= "\n"; $c++; } } return $str_xml; } /** * Data generation for combination charts * This function is called by fusioncharts_data * * @param array $data Data to be converted to XML * @param array $attributes See api.txt for details * @param string $jsid javascript id * @return string XML */ function _fusioncharts_data_combination($data, $attributes = array(), $jsid=NULL) { //validate and reform the data as necessary //for multi combination charts the validation needed is: // * that the value column is numeric // * that there is a value for each series // * that the axis contains either P or S // * that the axis must not cross over between categories // * that there is the same number of series for each category. foreach ($data as $column) { $series[$column[0]]++; $categories[$column[1]]++; $axis[$column[1]][$column[0]] = strtoupper($column[2]); $value[$column[1]][$column[0]] = $column[3]; $value_count++; $axis_crossover[strtoupper($column[2]) .'-'. $column[0]]++; if (!is_numeric($column[3])) { $numeric_error = TRUE; } if (!(strtoupper($column[2]) == 'S' || (strtoupper($column[2]) == 'P'))) { $axis_error = TRUE; } if (count($axis_crossover) != count($series)) { $axis_error = TRUE; } } if ((count($series) * count($categories)) != $value_count || $numeric_error == TRUE || $axis_error == TRUE) { drupal_set_message(t('The data for this chart is incomplete or contains an error. Graph cannot be drawn.'), 'error');//error - some data missing return; } else { //reconstruct the data so its in the right form foreach ($series as $s => $d) { foreach ($categories as $category => $d){ $clean_data[] = array($s, $category, $axis[$category][$s], $value[$category][$s]); } } } unset($series, $categories, $axis, $value, $category); //build xml $c=0; $str_xml = ''; foreach ($clean_data as $column) { if ($column[2] == 'S') { $category[] = $column[1]; } else { $categories[] = $column[1]; $category[] = $column[1]; } $series[] = $column[0]; $axis[] = $column[2]; $value[] = $column[3]; } $str_xml .= "\n"; $categories = array_unique($categories); if (end($categories) == '') { array_pop($categories); } $category_count = count($categories); $dataset_count = count(array_unique($category)); $total_results = count($value); foreach ($categories as $this_category) { $str_xml .= "\n"; } $str_xml .= "\n"; for ($i=0; $i<$total_results; $i+=$dataset_count) { if ($series[$i] != '') { if (isset($attributes['color'][$c])) { $color = "color='{$attributes['color'][$c]}'"; } $str_xml .= "\n"; for ($j=$i; $j<($i+$category_count); $j++) { if ($value[$j] != '') { unset($link); if (isset($attributes['callback'])) { $link = "link='JavaScript:fusioncharts_clickbar("$jsid", "{$attributes['callback']}", "{$series[$j]}", "{$category[$j]}")'"; } if (is_string($attributes['link'][$series[$j]])) { $link = "link='". urlencode($attributes['link'][$series[$j]]). "'"; } elseif (isset($attributes['link'][$series[$j]][$categories[$j]])) { $link = "link='". urlencode($attributes['link'][$series[$j]][$categories[$j]]) ."'"; } $str_xml .= "\n"; } } $str_xml .= "\n"; $c++; } } return $str_xml; } /** * Chart rendering function * This is the core function to Fusion Charts. It handles all of rendering actions. This function should be the one that is called to initiate a chart * * @param string $param The callback function or an object containing the data * @param string $jsid Name to be used for the id attribute in the DOM (used for javascript manipulation) optional * @param array $args Any further parameters that get passed to the callback function (in the case of $param being a callback function) optional * @return string the HTML to render the chart */ function theme_fusionchart($param, $jsid = NULL, $args = array()) { static $fusioncharts_id; //incremental ID of each chart (static in case there is more then one chart on a page) if (!isset($fusioncharts_id)) { $fusioncharts_id = 1; } if (!isset($jsid)) { $jsid = "DrupalFusionChart_". $fusioncharts_id++; } $fc_data = fusioncharts_data($jsid, $param, $args); $chart_file = array( 'Column 3D' => 'FCF_Column3D.swf', 'Column 2D' => 'FCF_Column2D.swf', 'Line 2D' => 'FCF_Line.swf', 'Area 2D' => 'FCF_Area2D.swf', 'Bar 2D' => 'FCF_Bar2D.swf', 'Pie 2D' => 'FCF_Pie2D.swf', 'Pie 3D' => 'FCF_Pie3D.swf', 'Doughnut 2D' => 'FCF_Doughnut2D.swf', 'Multi-series Column 2D' => 'FCF_MSColumn2D.swf', 'Multi-series Column 3D' => 'FCF_MSColumn3D.swf', 'Multi-series Line 2D' => 'FCF_MSLine.swf', 'Multi-series Bar 2D' => 'FCF_MSBar2D.swf', 'Multi-series Area 2D' => 'FCF_MSArea2D.swf', 'Stacked Column 3D' => 'FCF_StackedColumn3D.swf', 'Stacked Column 2D' => 'FCF_StackedColumn2D.swf', 'Stacked Bar 2D' => 'FCF_StackedBar2D.swf', 'Stacked Area 2D' => 'FCF_StackedArea2D.swf', 'Multi-series Column 2D + Line - Dual Y Axis' => 'FCF_MSColumn2DLineDY.swf', 'Multi-series Column 3D + Line - Dual Y Axis' => 'FCF_MSColumn3DLineDY.swf', 'Candlestick Chart' => 'FCF_Candlestick.swf', 'Funnel Chart' => 'FCF_Funnel.swf', 'Gantt Chart' => 'FCF_Gantt.swf' ); include_once(drupal_get_path('module', 'fusioncharts') ."/FusionChartsFree/Code/PHP/Includes/FusionCharts.php"); //check that the fusioncharts free package was installed if (!function_exists('renderChart')) { return t('Please install the Fusion Charts Free package from InfoSoft as per instructions in the readme.'); } $flashsource = base_path() . drupal_get_path('module', 'fusioncharts') ."/FusionChartsFree/Charts/". $chart_file[$fc_data->chart_type]; drupal_add_js(drupal_get_path('module', 'fusioncharts') .'/FusionChartsFree/JSClass/FusionCharts.js', 'module'); drupal_add_js(drupal_get_path('module', 'fusioncharts') .'/fusioncharts.js', 'module'); return renderChartHTML($flashsource, "", $fc_data->xml, $jsid, $fc_data->width, $fc_data->height); } /** * Backwards compatability function */ function fusioncharts_render($param, $jsid = NULL, $args = array()) { return theme('fusionchart', $param, $jsid, $args); } /** * Implementation of hook_theme */ function fusioncharts_theme($existing, $type, $theme, $path) { return array( 'fusionchart' => array( 'arguments' => array($param, $jsid = NULL, $args = array()), ), 'fusioncharts_matrix_table_form' => array( 'arguments' => array($form = array()), ), 'fc_matrixfield' => array( 'arguments' => array('element' => NULL), ), ); } /** * Fusioncharts Settings * Returns an array of setting. This used to be a drupal variable but some people reported issues so it now a function * @param string $type The name of the setting to retrieve * @result array the data */ function fusioncharts_settings($type) { switch ($type) { case 'fusioncharts': return array( 'Single Series Charts' => array( 'Column 3D' => 'Column 3D', 'Column 2D' => 'Column 2D', 'Line 2D' => 'Line 2D', 'Area 2D' => 'Area 2D', 'Bar 2D' => 'Bar 2D', 'Pie 2D' => 'Pie 2D', 'Pie 3D' => 'Pie 3D', 'Doughnut 2D' => 'Doughnut 2D', ), 'Multi-series Charts' => array( 'Multi-series Column 2D' => 'Multi-series Column 2D', 'Multi-series Column 3D' => 'Multi-series Column 3D', 'Multi-series Line 2D' => 'Multi-series Line 2D', 'Multi-series Bar 2D' => 'Multi-series Bar 2D', 'Multi-series Area 2D' => 'Multi-series Area 2D' ), 'Stacked Charts' => array( 'Stacked Column 3D' => 'Stacked Column 3D', 'Stacked Column 2D' => 'Stacked Column 2D', 'Stacked Bar 2D' => 'Stacked Bar 2D', 'Stacked Area 2D' => 'Stacked Area 2D' ), 'Combination Charts' => array( 'Multi-series Column 2D + Line - Dual Y Axis' => 'Multi-series Column 2D + Line - Dual Y Axis', 'Multi-series Column 3D + Line - Dual Y Axis' => 'Multi-series Column 3D + Line - Dual Y Axis' ), // 'Financial Charts' => array( // 'Candlestick Chart' => 'Candlestick Chart' // ), 'Funnel Chart' => array( 'Funnel Chart' => 'Funnel Chart' ), // 'Gantt Chart' => array( // 'Gantt Chart' => 'Gantt Chart' // ) ); case 'fusioncharts-defset': return array( 'bgcolor' => '#f3f3f3', 'bgAlpha' => '70', 'canvasBgColor' => '#f3fff3', 'canvasBorderColor' => '#000000', 'canvasBorderThickness' => '1', 'showCanvasBg' => '1', 'showCanvasBase' => '1', 'shownames' => '1', 'showValues' => '1', 'animation' => '1', 'showLimits' => '0', 'showLegend' => '1', 'rotateNames' => '0', 'showColumnShadow' => '1', 'baseFont' => 'Arial', 'baseFontSize' => '10', 'baseFontColor' => '#000000', 'outCnvBaseFont' => 'Arial', 'outCnvBaseFontSze' => '14', 'outCnvBaseFontColor' => '#000000', 'showhovercap' => '1', 'hoverCapBgColor' => '#ffffff', 'hoverCapBorderColor' => '#000000', 'zeroPlaneColor' => '#000000', 'formatNumber' => '1', 'formatNumberScale' => '1', 'decimalSeparator' => '.', 'thousandSeparator' => ',', 'decimalPrecision' => '0', 'divLineDecimalPrecision' => '0', 'limitsDecimalPrecision' => '0', 'divlinecolor' => '#000000', 'showDivLineValue' => '1', 'showAlternateHGridColor' => '0', 'alternateHGridColor' => '#000000', 'VDivlinecolor' => '#000000', 'showAlternateVGridColor' => '0', 'alternateVGridColor' => '#000000', ); case 'fusioncharts-defattr': return array( 'color' => array( 'color1' => '#AFD8F8', 'color2' => '#F6BD0F', 'color3' => '#8BBA00', 'color4' => '#FF8E46', 'color5' => '#008E8E', 'color6' => '#D64646', 'color7' => '#8E468E', 'color8' => '#588526', 'color9' => '#B3AA00', 'color10' => '#008ED6', 'color11' => '#9D080D', 'color12' => '#A186BE' ) ); } } /** * Implementation of hook_requirements(). */ function fusioncharts_requirements($phase) { $requirements = array(); if ($phase == 'runtime') { // Fusionchart SWF files $swf = glob(drupal_get_path('module', 'fusioncharts') .'/FusionChartsFree/Charts/*.swf'); if (!empty($swf)) { $requirements['fusioncharts']['severity'] = REQUIREMENT_OK; $requirements['fusioncharts']['value'] = t('The FusionCharts SWF files found.'); } else { $requirements['fusioncharts']['severity'] = REQUIREMENT_ERROR; $requirements['fusioncharts']['value'] = t('The FusionCharts SWF files not found.'); $requirements['fusioncharts']['description'] = t('Please download these files from http://www.fusioncharts.com/free and copy them into the fusioncharts module directory as per instructions in the readme.txt file.', array('@url' => 'http://www.fusioncharts.com/free')); } $requirements['fusioncharts']['title'] = t('FusionCharts'); } return $requirements; } /* * Helper function for ahah forms. Something like this should be in core!!!!! * http://www.nicklewis.org/node/967 */ if (!function_exists('ahah_render')) { function ahah_render($fields, $name) { $form_state = array('submitted' => FALSE); $form_build_id = $_POST['form_build_id']; // Add the new element to the stored form. Without adding the element to the // form, Drupal is not aware of this new elements existence and will not // process it. We retreive the cached form, add the element, and resave. $form = form_get_cache($form_build_id, $form_state); $form[$name] = $fields; form_set_cache($form_build_id, $form, $form_state); $form += array( '#post' => $_POST, '#programmed' => FALSE, ); // Rebuild the form. $form = form_builder($_POST['form_id'], $form, $form_state); // Render the new output. $new_form = $form[$name]; return drupal_render($new_form); } } /** * Convert all non-safe characters to their safe equivilents * * @param $string string to translate * @return $string */ function fusioncharts_entities($string) { $original = array("%", "&", "'", ":", "/", "@"); $entities = array("%25", "%26", "%27", "%3A", "%2F", "%40"); $string = htmlentities($string, ENT_QUOTES, 'UTF-8'); return str_replace($original, $entities, $string); }