|  | <?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}Y")); | 
|  | $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>" ); | 
|  |  | 
|  | ?> |