| <?php |
| /** |
| * ***************************************************************************** |
| * Copyright (c) 2016, 2017 Eclipse Foundation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * ***************************************************************************** |
| */ |
| |
| /* |
| * This file defines some functions for generating various sorts of |
| * charts using the Google Charts API. When this file is loaded, it |
| * adds the necessary JavaScript to pull in the APIs to the page. The |
| * individual functions provide relatively high-level APIs for |
| * generating content. |
| * |
| * The $App variable must be defined before this file is included or |
| * required. |
| */ |
| require_once dirname ( __FILE__ ) . "/../classes/Project.class.php"; |
| require_once dirname ( __FILE__ ) . '/../classes/database.inc'; |
| |
| class ChartContext { |
| var $start, $end; |
| |
| function __construct($age = 5) { |
| $this->start = new DateTime('first day of this month'); |
| $this->start->sub(new DateInterval("P{$age}M")); |
| $this->end = new DateTime('last day of previous month'); |
| } |
| |
| function getCurrent() { |
| return $this->getEnd(); |
| } |
| |
| function getStart() { |
| return clone $this->start; |
| } |
| |
| function getEnd() { |
| return clone $this->end; |
| } |
| |
| /** |
| * Convenience function to create a date from an expression. |
| * Primarily, this function exists because you can't dispatch |
| * a message to the result of calling a constructor, which makes |
| * inlining a date expression impossible. |
| * |
| * e.g. $context->getDateTime('sunday last week')->format('Y-m-d'); |
| * |
| * @param string $expression |
| * @return DateTime |
| */ |
| function getDateTime($expression) { |
| return new DateTime($expression); |
| } |
| |
| /** |
| * Get information regarding the last complete quarter before |
| * the a particular date. |
| * |
| * @internal |
| * @param int $date UNIX date |
| * @return Quarter |
| */ |
| private function getLastQuarter($date) { |
| $year = date ( 'Y', $date ); |
| $current = ceil ( date ( 'n', $date ) / 3 ); |
| $last = $current - 1; |
| if ($last < 1) { |
| $year --; |
| $last = 4; |
| } |
| |
| $start = str_pad ( $last * 3 - 2, 2, '0', STR_PAD_LEFT ); |
| $end = str_pad ( $last * 3, 2, '0', STR_PAD_LEFT ); |
| |
| return new Quarter("${year}Q${last}",new DateTime("${year}${start}"), |
| "${year}${end}" |
| ); |
| } |
| } |
| |
| /** |
| * This helper function turns a string expressing a year/month |
| * combination (e.g. '2017-03' or '201703') into a more human readable |
| * form (e.g. 'March 2017'). |
| * |
| * @param string $period |
| * @return string |
| */ |
| function asYearMonth($period) { |
| // TODO This function probably belongs in a different file. |
| if (! preg_match ( '/(\d\d\d\d)\-?(\d\d)/', $period, $matches )) |
| return $period; |
| $date = strtotime ( $matches [1] . '-' . $matches [2] . '-01' ); |
| return date ( 'M Y', $date ); |
| } |
| |
| /** |
| * Get information regarding the quarter that occurs before the |
| * provided date. The returned value is an array that includes |
| * the name of the quarter (e.g. '2017Q1'), and the start and |
| * end year/month (e.g. '201701'). This format is very particular |
| * to the specific implementation of database tables that represent |
| * queries for commit statics (i.e. this is probably not a |
| * very generally-useable function). |
| * |
| * @internal |
| * @param int $date UNIX date |
| * @return string[] |
| */ |
| function getLastQuarter($date) { |
| // TODO This function probably belongs in a different file. |
| $year = date ( 'Y', $date ); |
| $current = ceil ( date ( 'n', $date ) / 3 ); |
| $last = $current - 1; |
| if ($last < 1) { |
| $year --; |
| $last = 4; |
| } |
| |
| $start = str_pad ( $last * 3 - 2, 2, '0', STR_PAD_LEFT ); |
| $end = str_pad ( $last * 3, 2, '0', STR_PAD_LEFT ); |
| |
| return array ( |
| "${year}Q${last}", |
| "${year}${start}", |
| "${year}${end}" |
| ); |
| } |
| |
| /** |
| * Draw a pie chart. This function emits PHP code directly into |
| * the output stream and adds a bit of JavaScript into the page header. |
| * By default, the chart consumes 100% of the available width and |
| * most of the available height. These options can be overridden |
| * or augmented. |
| * |
| * <p>The global $App variable must be defined. |
| * |
| * <pre> |
| * $columns = array ( |
| * array ( |
| * 'label' => 'Project', |
| * 'type' => 'string' |
| * ), |
| * array ( |
| * 'label' => 'CQs', |
| * 'type' => 'number' |
| * ) |
| * ); |
| * $values = array( |
| * array('Dash', 14), |
| * array('Equinox', 5) |
| * ); |
| * drawPieChart('Projects', 'Projects and Numbers', $columns, $rows); |
| * </pre> |
| * |
| * @param string $id div identifer. Must be unique on the page |
| * @param string $title Title rendered at the top of the chart |
| * @param mixed $columns Array with name and type of columns |
| * @param mixed $values Array of arrays with values to render |
| * @param array $options Optional options. |
| */ |
| function drawPieChart($id, $title, $columns, $values, $options = array()) { |
| $defaultOptions = array ( |
| 'title' => $title, |
| 'pieSliceText' => 'label', |
| 'legend' => array('position' => 'labeled'), |
| 'width' => 800, |
| 'height' => 600, |
| 'chartArea' => array('width' => '100%', 'top' => '10%', 'height'=>'85%') |
| ); |
| $options = array_merge ( $defaultOptions, $options ); |
| drawChart ( 'Pie', $id, $title, $columns, $values, $options ); |
| } |
| |
| /** |
| * Draw a line chart. This function emits PHP code directly into |
| * the output stream and adds a bit of JavaScript into the page header. |
| * |
| * The first column (and corresponding values) are used to label |
| * the horizontal axis. All other columns are values related to that |
| * label. |
| * |
| * <p>The global $App variable must be defined. |
| * |
| * <pre> |
| * $columns = array ( |
| * array ( |
| * 'label' => 'Month', |
| * 'type' => 'string' |
| * ), |
| * array ( |
| * 'label' => 'Count', |
| * 'type' => 'number' |
| * ) |
| * ); |
| * $values = array( |
| * array('Dash', 14), |
| * array('Equinox', 5) |
| * ); |
| * drawLineChart('Projects', 'Projects and Numbers', $columns, $rows); |
| * </pre> |
| * |
| * @param string $id div identifer. Must be unique on the page |
| * @param string $title Title rendered at the top of the chart |
| * @param mixed $columns Array with name and type of columns |
| * @param mixed $values Array of arrays with values to render |
| * @param array $options Optional options. |
| */ |
| function drawLineChart($id, $title, $columns, $values, $options = array()) { |
| $defaultOptions = array ( |
| 'title' => $title, |
| 'width' => 800, |
| 'height' => 600 |
| ); |
| $options = array_merge ( $defaultOptions, $options ); |
| drawChart ( 'Line', $id, $title, $columns, $values, $options ); |
| } |
| |
| function drawBarChart($id, $title, $columns, $values, $options = array()) { |
| $defaultOptions = array ( |
| 'title' => $title, |
| 'width' => 800, |
| 'height' => 600 |
| ); |
| $options = array_merge ( $defaultOptions, $options ); |
| drawChart ( 'Bar', $id, $title, $columns, $values, $options ); |
| } |
| |
| function drawChart($chartType, $id, $title, $columns, $values, $options) { |
| global $App; |
| |
| $columnsJS = ''; |
| foreach ( $columns as $definition ) { |
| $type = $definition ['type']; |
| $label = $definition ['label']; |
| $columnsJS .= "data.addColumn('$type', '$label');"; |
| } |
| |
| $valuesJSON = json_encode ( $values, JSON_NUMERIC_CHECK ); |
| $optionsJSON = json_encode ( $options, JSON_NUMERIC_CHECK ); |
| $js = " |
| google.setOnLoadCallback(draw${id}Chart); |
| function draw${id}Chart() { |
| // Create the data table. |
| var data = new google.visualization.DataTable(); |
| $columnsJS; |
| data.addRows($valuesJSON); |
| |
| // Set chart options |
| var options = $optionsJSON; |
| |
| // Instantiate and draw our chart, passing in some options. |
| var chart = new google.visualization.{$chartType}Chart(document.getElementById('${id}_div')); |
| chart.draw(data, options); |
| |
| document.getElementById('${id}_div_png').innerHTML = '<a href=\"' + chart.getImageURI() + '\">Printable version</a>'; |
| }"; |
| |
| $App->addExtraHtmlHeader ( "<script type=\"text/javascript\">$js</script>" ); |
| |
| echo "<div id=\"${id}_div_png\"></div>"; |
| echo "<div id=\"${id}_div\"></div>"; |
| } |
| |
| /** |
| * This class implements a constructor pattern for building |
| * charts. All of the functions answer the receiver so that |
| * calls can be chained together. Most of the functions are |
| * concerned with configuring the chart; the |
| * <code>render()</code> renders the actual chart. Note that |
| * the rendering ends up being a combination of JavaScript in |
| * the header and some <code><div></code> tags in |
| * the content. |
| * |
| * <pre>ChartBuilder::named('chart_id') |
| * ->title('The Chart Title') |
| * ->query('dashboard', 'select field1, field2 from ...') |
| * ->column('Field One', 'field1', 'number') |
| * ->render();</pre> |
| * |
| */ |
| class ChartBuilder { |
| var $name; |
| var $title = ''; |
| var $description = ''; |
| var $dataFunction; |
| var $substitutions = array(); |
| var $type = 'Line'; |
| var $columns = array(); |
| var $columnFields = array(); |
| var $options = array(); |
| |
| var $titleString = "<h3 id=\":id\">:title</h3>"; |
| |
| public static function named($name) { |
| return new ChartBuilder($name); |
| } |
| |
| private function __construct($name) { |
| $this->name = $name; |
| $this->options = array ( |
| 'width' => 800, |
| 'height' => 600, |
| 'legend' => array('position' => 'top'), |
| 'chartArea' => array('width' => '80%', 'top' => '10%', 'bottom' => '10%', 'height'=>'80%') |
| ); |
| } |
| |
| public function title($title) { |
| $this->title = $title; |
| return $this; |
| } |
| |
| public function description($description) { |
| $this->description = $description; |
| return $this; |
| } |
| |
| public function dataFunction($callable) { |
| $this->dataFunction = $callable; |
| return $this; |
| } |
| |
| public function query($database, $query) { |
| $receiver = $this; |
| $this->dataFunction = function() use (&$database, &$query, &$receiver) { |
| $rows = array(); |
| query ( $database, $query, $this->substitutions, function ($row) use (&$receiver, &$rows) { |
| $values = array (); |
| foreach ( $receiver->columnFields as $field => $function ) { |
| $values [] = call_user_func( $function, $row [$field] ); |
| } |
| $rows [] = $values; |
| } ); |
| return $rows; |
| }; |
| return $this; |
| } |
| |
| public function substitute($key, $value) { |
| if ($value !== null) |
| $this->substitutions[$key] = $value; |
| return $this; |
| } |
| |
| public function pieChart() { |
| $this->type = 'Pie'; |
| $this->options = array_merge($this->options, |
| array ( |
| 'pieSliceText' => 'label', |
| 'legend' => array('position' => 'labeled'), |
| 'chartArea' => array('width' => '100%', 'top' => '10%', 'height'=>'85%') |
| )); |
| return $this; |
| } |
| |
| public function barChart() { |
| $this->type = 'Bar'; |
| return $this; |
| } |
| |
| public function columnChart() { |
| $this->type = 'Column'; |
| return $this; |
| } |
| |
| /** |
| * Provide information about a single column. |
| * |
| * @param string $label label for the column. |
| * @param string $field name of the field in the SQL query |
| * @param string $type type of the column, e.g. 'number' or 'string' |
| * @param callable $function Optional function to convert the field value before rendering in the chart. |
| * |
| * @return ChartBuilder The receiver. |
| */ |
| public function column($label, $field, $type = 'number', $function = null) { |
| $this->columns[] = array('label' => $label, 'type' => $type); |
| $this->columnFields[$field] = $function == null ? function($value) { return $value; } : $function; |
| return $this; |
| } |
| |
| public function option($key, $value) { |
| $this->options[$key] = $value; |
| return $this; |
| } |
| |
| public function titleString($value) { |
| $this->titleString = $value; |
| return $this; |
| } |
| |
| public function height($value) { |
| return $this->option('height', $value); |
| } |
| |
| public function width($value) { |
| return $this->option('width', $value); |
| } |
| |
| /** |
| * Render the chart with the information contained in the receiver. |
| */ |
| public function render() { |
| $rows = call_user_func($this->dataFunction); |
| |
| $options = array ( |
| 'curveType' => 'function', |
| 'vAxis' => array ( |
| 'viewWindowMode' => 'explicit', |
| 'viewWindow' => array ( |
| 'min' => 0 |
| ) |
| ) |
| ); |
| |
| $options = array_merge($this->options, $options); |
| |
| if ($this->title) { |
| $title = strtr($this->title, $this->substitutions); |
| echo strtr($this->titleString, array(':id' => $this->name, ':title' => $title)); |
| } |
| if ($this->description) { |
| $description = strtr($this->description, $this->substitutions); |
| echo "<p>{$description}</p>"; |
| } |
| |
| drawChart ( $this->type, $this->name, $title, $this->columns, $rows, $options ); |
| |
| // $function = "draw{$this->type}Chart"; |
| // $function ( $this->name, $title, $this->columns, $rows, $options ); |
| } |
| } |
| |
| $App->addExtraHtmlHeader ( "<script type=\"text/javascript\" src=\"https://www.google.com/jsapi\"></script>" ); |
| $App->addExtraHtmlHeader ( "<script type=\"text/javascript\">google.load('visualization', '1.0', {'packages':['corechart']});</script>" ); |
| |
| ?> |