diff --git a/oss/piwik/trunk/core/ViewDataTable/Cloud.php b/oss/piwik/trunk/core/ViewDataTable/Cloud.php
new file mode 100644
index 00000000..735f9f5f
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/Cloud.php
@@ -0,0 +1,124 @@
+displayLogoInsteadOfLabel = $bool;
+ }
+
+ protected function getViewDataTableId()
+ {
+ return 'cloud';
+ }
+
+ /**
+ * @see Piwik_ViewDataTable::init()
+ */
+ function init($currentControllerName,
+ $currentControllerAction,
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable = null)
+ {
+ parent::init($currentControllerName,
+ $currentControllerAction,
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable);
+ $this->dataTableTemplate = 'CoreHome/templates/cloud.tpl';
+ $this->disableOffsetInformation();
+ $this->disableExcludeLowPopulation();
+ }
+
+ /**
+ * @see Piwik_ViewDataTable::main()
+ *
+ */
+ public function main()
+ {
+ $this->setLimit( 30 );
+ if($this->mainAlreadyExecuted)
+ {
+ return;
+ }
+ $this->mainAlreadyExecuted = true;
+
+ $this->isDataAvailable = true;
+ try {
+ $this->loadDataTableFromAPI();
+ } catch(Exception $e) {
+ $this->isDataAvailable = false;
+ }
+ $this->view = $this->buildView();
+ }
+
+ function getColumnToDisplay()
+ {
+ $columns = parent::getColumnsToDisplay();
+ // not label, but the first numeric column
+ return $columns[1];
+ }
+
+ protected function buildView()
+ {
+ $view = new Piwik_View($this->dataTableTemplate);
+ if(!$this->isDataAvailable)
+ {
+ $view->cloudValues = array();
+ }
+ else
+ {
+ $columnToDisplay = $this->getColumnToDisplay();
+ $columnTranslation = $this->getColumnTranslation($columnToDisplay);
+ $values = $this->dataTable->getColumn($columnToDisplay);
+ $labels = $this->dataTable->getColumn('label');
+ $labelMetadata = array();
+ foreach($this->dataTable->getRows() as $row)
+ {
+ $logo = false;
+ if($this->displayLogoInsteadOfLabel)
+ {
+ $logo = $row->getMetadata('logo');
+ }
+ $labelMetadata[$row->getColumn('label')] = array(
+ 'logo' => $logo,
+ 'url' => $row->getMetadata('url'),
+ );
+ }
+ $cloud = new Piwik_Visualization_Cloud();
+ foreach($labels as $i => $label)
+ {
+ $cloud->addWord($label, $values[$i]);
+ }
+ $cloudValues = $cloud->render('array');
+ foreach($cloudValues as &$value)
+ {
+ $value['logoWidth'] = round(max(16, $value['percent']));
+ }
+ $view->columnTranslation = $columnTranslation;
+ $view->labelMetadata = $labelMetadata;
+ $view->cloudValues = $cloudValues;
+ }
+ $view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
+ $view->properties = $this->getViewProperties();
+ return $view;
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData.php
new file mode 100644
index 00000000..90e3366a
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData.php
@@ -0,0 +1,155 @@
+
+ * function getWebsites( $fetch = false)
+ * {
+ * $view = Piwik_ViewDataTable::factory();
+ * $view->init( $this->pluginName, 'getWebsites', 'Referers.getWebsites', 'getUrlsFromWebsiteId' );
+ * $view->setColumnsToDisplay( array('label','nb_visits') );
+ * $view->setLimit(10);
+ * $view->setGraphLimit(12);
+ * return $this->renderView($view, $fetch);
+ * }
+ *
+ *
+ * @package Piwik
+ * @subpackage Piwik_ViewDataTable
+ */
+abstract class Piwik_ViewDataTable_GenerateGraphData extends Piwik_ViewDataTable
+{
+ /**
+ * Number of elements to display in the graph.
+ * @var int
+ */
+ protected $graphLimit = null;
+ protected $yAxisUnit = '';
+
+ public function setAxisYUnit($unit)
+ {
+ $this->yAxisUnit = $unit;
+ }
+
+ /**
+ * Sets the number max of elements to display (number of pie slice, vertical bars, etc.)
+ * If the data has more elements than $limit then the last part of the data will be the sum of all the remaining data.
+ *
+ * @param int $limit
+ */
+ public function setGraphLimit( $limit )
+ {
+ $this->graphLimit = $limit;
+ }
+
+ /**
+ * Returns numbers of elemnts to display in the graph
+ *
+ * @return int
+ */
+ public function getGraphLimit()
+ {
+ return $this->graphLimit;
+ }
+
+ protected $displayPercentageInTooltip = true;
+
+ /**
+ * The percentage in tooltips is computed based on the sum of all values for the plotted column.
+ * If the sum of the column in the data set is not the number of elements in the data set,
+ * for example when plotting visits that have a given plugin enabled:
+ * one visit can have several plugins, hence the sum is much greater than the number of visits.
+ * In this case displaying the percentage doesn't make sense.
+ */
+ public function disallowPercentageInGraphTooltip()
+ {
+ $this->displayPercentageInTooltip = false;
+ }
+
+ public function main()
+ {
+ if($this->mainAlreadyExecuted)
+ {
+ return;
+ }
+ $this->mainAlreadyExecuted = true;
+
+ if (!Zend_Registry::get('config')->General->serve_widget_and_data)
+ {
+ @header( "Content-Type: application/json" );
+ }
+
+ // Graphs require the full dataset, setting limit to null (same as 'no limit')
+ $this->setLimit(null);
+
+ // the queued filters will be manually applied later. This is to ensure that filtering using search
+ // will be done on the table before the labels are enhanced (see ReplaceColumnNames)
+ $this->disableQueuedFilters();
+
+ // throws exception if no view access
+ $this->loadDataTableFromAPI();
+
+ $graphLimit = $this->getGraphLimit();
+ if(!empty($graphLimit))
+ {
+ $offsetStartSummary = $this->getGraphLimit() - 1;
+ $this->dataTable->filter('AddSummaryRow',
+ array($offsetStartSummary,
+ Piwik_Translate('General_Others'),
+ Piwik_Archive::INDEX_NB_VISITS
+ )
+ );
+ }
+ $this->isDataAvailable = $this->dataTable->getRowsCount() != 0;
+
+ if(!$this->isDataAvailable)
+ {
+ $this->view->setTitle(html_entity_decode(Piwik_Translate('General_NoDataForGraph'), ENT_COMPAT, 'UTF-8'), '{font-size: 25px;}');
+ }
+ else
+ {
+ $this->initChartObjectData();
+ }
+ $this->view->customizeChartProperties();
+ }
+
+ protected function initChartObjectData()
+ {
+ $this->dataTable->applyQueuedFilters();
+
+ // We apply a filter to the DataTable, decoding the label column (useful for keywords for example)
+ $this->dataTable->filter('ColumnCallbackReplace', array('label','urldecode'));
+
+ $xLabels = $this->dataTable->getColumn('label');
+ $columnNames = parent::getColumnsToDisplay();
+ if(($labelColumnFound = array_search('label',$columnNames)) !== false)
+ {
+ unset($columnNames[$labelColumnFound]);
+ }
+
+ $columnNameToTranslation = $columnNameToValue = array();
+ foreach($columnNames as $columnName)
+ {
+ $columnNameToTranslation[$columnName] = $this->getColumnTranslation($columnName);
+ $columnNameToValue[$columnName] = $this->dataTable->getColumn($columnName);
+ }
+ $this->view->setAxisXLabels($xLabels);
+ $this->view->setAxisYValues($columnNameToValue);
+ $this->view->setAxisYLabels($columnNameToTranslation);
+ $this->view->setAxisYUnit($this->yAxisUnit);
+ $this->view->setDisplayPercentageInTooltip($this->displayPercentageInTooltip);
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartEvolution.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
new file mode 100644
index 00000000..a4d774bf
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
@@ -0,0 +1,216 @@
+view = new Piwik_Visualization_Chart_Evolution();
+ }
+
+ protected function guessUnitFromRequestedColumnNames($requestedColumnNames, $idSite)
+ {
+ $nameToUnit = array(
+ '_rate' => '%',
+ '_revenue' => Piwik::getCurrency($idSite),
+ );
+ foreach($requestedColumnNames as $columnName)
+ {
+ foreach($nameToUnit as $pattern => $type)
+ {
+ if(strpos($columnName, $pattern) !== false)
+ {
+ return $type;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected function initChartObjectData()
+ {
+ // if the loaded datatable is a simple DataTable, it is most likely a plugin plotting some custom data
+ // we don't expect plugin developers to return a well defined Piwik_DataTable_Array
+ if($this->dataTable instanceof Piwik_DataTable)
+ {
+ return parent::initChartObjectData();
+ }
+
+ $this->dataTable->applyQueuedFilters();
+ if(!($this->dataTable instanceof Piwik_DataTable_Array))
+ {
+ throw new Exception("Expecting a DataTable_Array with custom format to draw an evolution chart");
+ }
+
+ // the X label is extracted from the 'period' object in the table's metadata
+ $xLabels = $uniqueIdsDataTable = array();
+ foreach($this->dataTable->metadata as $idDataTable => $metadataDataTable)
+ {
+ //eg. "Aug 2009"
+ $xLabels[] = html_entity_decode($metadataDataTable['period']->getLocalizedShortString(), ENT_COMPAT, 'UTF-8');
+ // we keep track of all unique data table that we need to set a Y value for
+ $uniqueIdsDataTable[] = $idDataTable;
+ }
+
+ $requestedColumnNames = $this->getColumnsToDisplay();
+ $yAxisLabelToValue = array();
+ foreach($this->dataTable->getArray() as $idDataTable => $dataTable)
+ {
+ foreach($dataTable->getRows() as $row)
+ {
+ $rowLabel = $row->getColumn('label');
+ foreach($requestedColumnNames as $requestedColumnName)
+ {
+ $metricLabel = $this->getColumnTranslation($requestedColumnName);
+ if($rowLabel !== false)
+ {
+ // eg. "Yahoo! (Visits)"
+ $yAxisLabel = "$rowLabel ($metricLabel)";
+ }
+ else
+ {
+ // eg. "Visits"
+ $yAxisLabel = $metricLabel;
+ }
+ if(($columnValue = $row->getColumn($requestedColumnName)) !== false)
+ {
+ $yAxisLabelToValue[$yAxisLabel][$idDataTable] = $columnValue;
+ }
+ }
+ }
+ }
+
+ // make sure all column values are set to at least zero (no gap in the graph)
+ $yAxisLabelToValueCleaned = array();
+ $yAxisLabels = array();
+ foreach($uniqueIdsDataTable as $uniqueIdDataTable)
+ {
+ foreach($yAxisLabelToValue as $yAxisLabel => $idDataTableToColumnValue)
+ {
+ $yAxisLabels[$yAxisLabel] = $yAxisLabel;
+ if(isset($idDataTableToColumnValue[$uniqueIdDataTable]))
+ {
+ $columnValue = $idDataTableToColumnValue[$uniqueIdDataTable];
+ }
+ else
+ {
+ $columnValue = 0;
+ }
+ $yAxisLabelToValueCleaned[$yAxisLabel][] = $columnValue;
+ }
+ }
+ $idSite = Piwik_Common::getRequestVar('idSite');
+
+ $unit = $this->yAxisUnit;
+ if(empty($unit))
+ {
+ $unit = $this->guessUnitFromRequestedColumnNames($requestedColumnNames, $idSite);
+ }
+
+ $this->view->setAxisXLabels($xLabels);
+ $this->view->setAxisYValues($yAxisLabelToValueCleaned);
+ $this->view->setAxisYLabels($yAxisLabels);
+ $this->view->setAxisYUnit($unit);
+
+ $firstDatatable = reset($this->dataTable->metadata);
+ $period = $firstDatatable['period'];
+ switch($period->getLabel()) {
+ case 'day': $steps = 7; break;
+ case 'week': $steps = 10; break;
+ case 'month': $steps = 6; break;
+ case 'year': $steps = 2; break;
+ default: $steps = 10; break;
+ }
+ $this->view->setXSteps($steps);
+
+ if($this->isLinkEnabled())
+ {
+ $axisXOnClick = array();
+ $queryStringAsHash = $this->getQueryStringAsHash();
+ foreach($this->dataTable->metadata as $idDataTable => $metadataDataTable)
+ {
+ $period = $metadataDataTable['period'];
+ $dateInUrl = $period->getDateStart();
+ $parameters = array(
+ 'idSite' => $idSite,
+ 'period' => $period->getLabel(),
+ 'date' => $dateInUrl->toString()
+ );
+ $hash = '';
+ if(!empty($queryStringAsHash))
+ {
+ $hash = '#' . Piwik_Url::getQueryStringFromParameters( $queryStringAsHash + $parameters);
+ }
+ $link = Piwik_Url::getCurrentUrlWithoutQueryString() .
+ '?' .
+ Piwik_Url::getQueryStringFromParameters( array(
+ 'module' => 'CoreHome',
+ 'action' => 'index',
+ ) + $parameters)
+ . $hash;
+ $axisXOnClick[] = $link;
+ }
+ $this->view->setAxisXOnClick($axisXOnClick);
+ }
+ }
+
+ /**
+ * We link the graph dots to the same report as currently being displayed (only the date would change).
+ *
+ * In some cases the widget is loaded within a report that doesn't exist as such.
+ * For example, the dashboards loads the 'Last visits graph' widget which can't be directly linked to.
+ * Instead, the graph must link back to the dashboard.
+ *
+ * In other cases, like Visitors>Overview or the Goals graphs, we can link the graph clicks to the same report.
+ *
+ * To detect whether or not we can link to a report, we simply check if the current URL from which it was loaded
+ * belongs to the menu or not. If it doesn't belong to the menu, we do not append the hash to the URL,
+ * which results in loading the dashboard.
+ *
+ * @return array Query string array to append to the URL hash or false if the dashboard should be displayed
+ */
+ private function getQueryStringAsHash()
+ {
+ $queryString = Piwik_Url::getArrayFromCurrentQueryString();
+ $piwikParameters = array('idSite', 'date', 'period', 'XDEBUG_SESSION_START', 'KEY');
+ foreach($piwikParameters as $parameter)
+ {
+ unset($queryString[$parameter]);
+ }
+ if(Piwik_IsMenuUrlFound($queryString))
+ {
+ return $queryString;
+ }
+ return false;
+ }
+
+ private function isLinkEnabled()
+ {
+ static $linkEnabled;
+ if(!isset($linkEnabled))
+ {
+ $linkEnabled = !Piwik_Common::getRequestVar('disableLink', 0, 'int');
+ }
+ return $linkEnabled;
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartPie.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartPie.php
new file mode 100644
index 00000000..1acda43f
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartPie.php
@@ -0,0 +1,32 @@
+view = new Piwik_Visualization_Chart_Pie();
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php
new file mode 100644
index 00000000..b2b6feed
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php
@@ -0,0 +1,32 @@
+view = new Piwik_Visualization_Chart_VerticalBar();
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML.php
new file mode 100644
index 00000000..1125620a
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML.php
@@ -0,0 +1,169 @@
+dataTableTemplate = 'CoreHome/templates/graph.tpl';
+
+ $this->disableOffsetInformation();
+ $this->disableExcludeLowPopulation();
+ $this->disableSearchBox();
+ $this->enableShowExportAsImageIcon();
+
+ $this->parametersToModify = array(
+ 'viewDataTable' => $this->getViewDataTableIdToLoad(),
+ // in the case this controller is being executed by another controller
+ // eg. when being widgetized in an IFRAME
+ // we need to put in the URL of the graph data the real module and action
+ 'module' => $currentControllerName,
+ 'action' => $currentControllerAction,
+ );
+ }
+
+ public function enableShowExportAsImageIcon()
+ {
+ $this->viewProperties['show_export_as_image_icon'] = true;
+ }
+
+ /**
+ * Sets parameters to modify in the future generated URL
+ * @param array $array array('nameParameter' => $newValue, ...)
+ */
+ public function setParametersToModify($array)
+ {
+ $this->parametersToModify = array_merge($this->parametersToModify, $array);
+ }
+
+ /**
+ * We persist the parametersToModify values in the javascript footer.
+ * This is used by the "export links" that use the "date" attribute
+ * from the json properties array in the datatable footer.
+ */
+ protected function getJavascriptVariablesToSet()
+ {
+ return $this->parametersToModify + parent::getJavascriptVariablesToSet();
+ }
+
+ /**
+ * @see Piwik_ViewDataTable::main()
+ */
+ public function main()
+ {
+ if($this->mainAlreadyExecuted)
+ {
+ return;
+ }
+ $this->mainAlreadyExecuted = true;
+
+ $this->view = $this->buildView();
+ }
+
+ protected function buildView()
+ {
+ $view = new Piwik_View($this->dataTableTemplate);
+ $this->uniqueIdViewDataTable = $this->getUniqueIdViewDataTable();
+ $view->graphType = $this->graphType;
+ $this->chartDivId = $this->uniqueIdViewDataTable . "Chart_swf";
+
+ $this->parametersToModify['action'] = $this->currentControllerAction;
+ $this->parametersToModify = array_merge($this->variablesDefault, $this->parametersToModify);
+
+ $url = Piwik_Url::getCurrentQueryStringWithParametersModified($this->parametersToModify);
+
+ $this->includeData = Zend_Registry::get('config')->General->serve_widget_and_data;
+ $idSite = Piwik_Common::getRequestVar('idSite', 1);
+ if(Piwik::isUserHasViewAccess($idSite) && $this->includeData)
+ {
+ $this->chartData = $this->getFlashData();
+ }
+ else
+ {
+ $this->chartData = null;
+ }
+ $view->flashParameters = $this->getFlashParameters();
+ $view->urlGraphData = $url;
+ $view->chartDivId = $this->chartDivId;
+ $view->formEmbedId = "formEmbed".$this->uniqueIdViewDataTable;
+ $view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
+ $view->properties = $this->getViewProperties();
+ return $view;
+ }
+
+ protected function getFlashData()
+ {
+ $saveGet = $_GET;
+
+ foreach($this->parametersToModify as $key => $val)
+ {
+ // We do not forward filter data to the graph controller.
+ // This would cause the graph to have filter_limit=5 set by default,
+ // which would break them (graphs need the full dataset to build the "Others" aggregate value)
+ if(strpos($key, 'filter_') !== false)
+ {
+ continue;
+ }
+ if (is_array($val))
+ {
+ $_GET[$key] = unserialize(serialize($val));
+ }
+ else
+ {
+ $_GET[$key] = $val;
+ }
+ }
+ $content = Piwik_FrontController::getInstance()->fetchDispatch( $this->currentControllerName, $this->currentControllerAction, array());
+
+ $_GET = $saveGet;
+
+ return str_replace(array("\r", "\n", "'", '\"'), array('', '', "\\'", '\\\"'), $content);
+ }
+
+ protected function getFlashParameters()
+ {
+ // chart title is only set when there's no data in the graph
+ $isDataAvailable = $this->chartData && !preg_match('/],\s+"title": {/', $this->chartData);
+
+ return array(
+ 'width' => $this->width,
+ 'height' => $this->height,
+ 'ofcLibraryPath' => 'libs/open-flash-chart/',
+ 'swfLibraryPath' => 'libs/swfobject/',
+ 'requiredFlashVersion' => '9.0.0',
+ 'isDataAvailable' => $isDataAvailable,
+ 'includeData' => $this->includeData,
+ 'data' => $this->chartData,
+ );
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
new file mode 100644
index 00000000..209c16e1
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
@@ -0,0 +1,83 @@
+width='100%';
+ $this->height=150;
+ // used for the CSS class to apply to the DIV containing the graph
+ $this->graphType = 'evolution';
+ }
+
+ protected function getViewDataTableId()
+ {
+ return 'graphEvolution';
+ }
+
+ protected function getViewDataTableIdToLoad()
+ {
+ return 'generateDataChartEvolution';
+ }
+
+ function init($currentControllerName,
+ $currentControllerAction,
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable = null)
+ {
+ parent::init($currentControllerName,
+ $currentControllerAction,
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable);
+
+ $this->setParametersToModify(array('date' => Piwik_Common::getRequestVar('date', 'last30', 'string')));
+ $this->disableShowAllViewsIcons();
+ $this->disableShowTable();
+ }
+
+
+ /**
+ * We ensure that the graph for a given Goal has a different ID than the 'Goals Overview' graph
+ * so that both can display on the dashboard at the same time
+ */
+ public function getUniqueIdViewDataTable()
+ {
+ $id = parent::getUniqueIdViewDataTable();
+ if(isset($this->parametersToModify['idGoal']))
+ {
+ $id .= $this->parametersToModify['idGoal'];
+ }
+ return $id;
+ }
+
+ /**
+ * Sets the columns that will be displayed on output evolution chart
+ * By default all columns are displayed ($columnsNames = array() will display all columns)
+ *
+ * @param array $columnsNames Array of column names eg. array('nb_visits','nb_hits')
+ */
+ public function setColumnsToDisplay( $columnsNames)
+ {
+ if(!is_array($columnsNames))
+ {
+ $columnsNames = array($columnsNames);
+ }
+ $this->setParametersToModify( array('columns' => $columnsNames) );
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML/ChartPie.php b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML/ChartPie.php
new file mode 100644
index 00000000..692bdf4f
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/GenerateGraphHTML/ChartPie.php
@@ -0,0 +1,30 @@
+dataTableTemplate = 'CoreHome/templates/datatable.tpl';
+ $this->variablesDefault['enable_sort'] = '1';
+ $this->setSortedColumn('nb_visits', 'desc');
+ $this->setLimit(Zend_Registry::get('config')->General->datatable_default_limit);
+ $this->handleLowPopulation();
+ }
+
+ protected function getViewDataTableId()
+ {
+ return 'table';
+ }
+
+ /**
+ * @see Piwik_ViewDataTable::main()
+ */
+ public function main()
+ {
+ if($this->mainAlreadyExecuted)
+ {
+ return;
+ }
+ $this->mainAlreadyExecuted = true;
+
+ $this->isDataAvailable = true;
+ try {
+ $this->loadDataTableFromAPI();
+ } catch(Exception $e) {
+ $this->isDataAvailable = false;
+ }
+
+ $this->postDataTableLoadedFromAPI();
+ $this->view = $this->buildView();
+ }
+
+ /**
+ * @return Piwik_View with all data set
+ */
+ protected function buildView()
+ {
+ $view = new Piwik_View($this->dataTableTemplate);
+
+ if(!$this->isDataAvailable)
+ {
+ $view->arrayDataTable = array();
+ }
+ else
+ {
+ $columns = $this->getColumnsToDisplay();
+ $columnTranslations = $columnDescriptions = array();
+ foreach($columns as $columnName)
+ {
+ $columnTranslations[$columnName] = $this->getColumnTranslation($columnName);
+ $columnDescriptions[$columnName] = $this->getColumnDescription($columnName);
+ }
+ $nbColumns = count($columns);
+ // case no data in the array we use the number of columns set to be displayed
+ if($nbColumns == 0)
+ {
+ $nbColumns = count($this->columnsToDisplay);
+ }
+
+ $view->arrayDataTable = $this->getPHPArrayFromDataTable();
+ $view->dataTableColumns = $columns;
+ $view->columnTranslations = $columnTranslations;
+ $view->columnDescriptions = $columnDescriptions;
+ $view->nbColumns = $nbColumns;
+ $view->defaultWhenColumnValueNotDefined = '-';
+ }
+ $view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
+ $view->properties = $this->getViewProperties();
+ return $view;
+ }
+
+ protected function handleLowPopulation( $columnToApplyFilter = null)
+ {
+ if(Piwik_Common::getRequestVar('enable_filter_excludelowpop', '0', 'string' ) == '0')
+ {
+ return;
+ }
+ if(is_null($columnToApplyFilter))
+ {
+ $columnToApplyFilter = Piwik_Archive::INDEX_NB_VISITS;
+ }
+ $this->setExcludeLowPopulation( $columnToApplyFilter);
+ }
+
+ /**
+ * Returns friendly php array from the Piwik_DataTable
+ * @see Piwik_DataTable_Renderer_Php
+ * @return array
+ */
+ protected function getPHPArrayFromDataTable()
+ {
+ $renderer = Piwik_DataTable_Renderer::factory('php');
+ $renderer->setTable($this->dataTable);
+ $renderer->setSerialize( false );
+ // we get the php array from the datatable but conserving the original datatable format,
+ // ie. rows 'columns', 'metadata' and 'idsubdatatable'
+ $phpArray = $renderer->originalRender();
+ return $phpArray;
+ }
+
+
+ /**
+ * Adds a column to the list of columns to be displayed
+ *
+ * @param string $columnName
+ */
+ public function addColumnToDisplay( $columnName )
+ {
+ $this->columnsToDisplay[] = $columnName;
+ }
+
+ /**
+ */
+ public function disableSort()
+ {
+ $this->variablesDefault['enable_sort'] = 'false';
+ }
+
+ /**
+ * Sets the search on a table to be recursive (also searches in subtables)
+ * Works only on Actions/Downloads/Outlinks tables.
+ *
+ * @return bool If the pattern for a recursive search was set in the URL
+ */
+ public function setSearchRecursive()
+ {
+ $this->variablesDefault['search_recursive'] = true;
+ return $this->setRecursiveLoadDataTableIfSearchingForPattern();
+ }
+
+ protected function getRequestString()
+ {
+ $requestString = parent::getRequestString();
+ if($this->recursiveDataTableLoad)
+ {
+ $requestString .= '&expanded=1';
+ }
+ return $requestString;
+ }
+
+ /**
+ * Set the flag to load the datatable recursively so we can search on subtables as well
+ *
+ * @return bool if recursive search is enabled
+ */
+ protected function setRecursiveLoadDataTableIfSearchingForPattern()
+ {
+ try{
+ $requestValue = Piwik_Common::getRequestVar('filter_column_recursive');
+ $requestValue = Piwik_Common::getRequestVar('filter_pattern_recursive');
+ // if the 2 variables are set we are searching for something.
+ // we have to load all the children subtables in this case
+
+ $this->recursiveDataTableLoad = true;
+ return true;
+ }
+ catch(Exception $e) {
+ $this->recursiveDataTableLoad = false;
+ return false;
+ }
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/HtmlTable/AllColumns.php b/oss/piwik/trunk/core/ViewDataTable/HtmlTable/AllColumns.php
new file mode 100644
index 00000000..c612025f
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/HtmlTable/AllColumns.php
@@ -0,0 +1,54 @@
+viewProperties['show_exclude_low_population'] = true;
+ parent::main();
+ }
+
+ protected function getRequestString()
+ {
+ $requestString = parent::getRequestString();
+ return $requestString . '&filter_add_columns_when_show_all_columns=1';
+ }
+
+ protected function postDataTableLoadedFromAPI()
+ {
+ parent::postDataTableLoadedFromAPI();
+ Piwik_Controller::setPeriodVariablesView($this);
+ $columnUniqueVisitors = false;
+ if($this->period == 'day')
+ {
+ $columnUniqueVisitors = 'nb_uniq_visitors';
+ }
+ $this->setColumnsToDisplay(array('label',
+ 'nb_visits',
+ $columnUniqueVisitors,
+ 'nb_actions_per_visit',
+ 'avg_time_on_site',
+ 'bounce_rate'));
+ $this->dataTable->filter('ColumnCallbackReplace', array('avg_time_on_site', create_function('$averageTimeOnSite', 'return Piwik::getPrettyTimeFromSeconds($averageTimeOnSite);')));
+ $this->dataTable->filter('ColumnCallbackReplace', array('bounce_rate', create_function('$bounceRate', 'return $bounceRate."%";')));
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/HtmlTable/Goals.php b/oss/piwik/trunk/core/ViewDataTable/HtmlTable/Goals.php
new file mode 100644
index 00000000..dfa2f581
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/HtmlTable/Goals.php
@@ -0,0 +1,157 @@
+idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
+ $this->processOnlyIdGoal = Piwik_Common::getRequestVar('filter_only_display_idgoal', 0, 'int');
+ $this->viewProperties['show_exclude_low_population'] = true;
+ $this->viewProperties['show_goals'] = true;
+
+ $this->setColumnsTranslations( array(
+ 'goal_%s_conversion_rate' => '%s conversion rate',
+ 'goal_%s_nb_conversions' => '%s conversions',
+ 'goal_%s_revenue_per_visit' => '%s revenue per visit',
+ ));
+
+ $this->setColumnsToDisplay( array(
+ 'label',
+ 'nb_visits',
+ 'goal_%s_nb_conversions',
+ 'goal_%s_conversion_rate',
+ 'goal_%s_revenue_per_visit',
+ 'goals_conversion_rate',
+ 'revenue_per_visit',
+ ));
+
+ // We ensure that the 'Sort by' column is actually displayed in the table
+ // eg. most daily reports sort by nb_uniq_visitors but this column is not displayed in the Goals table
+ $columnsToDisplay = $this->getColumnsToDisplay();
+ $columnToSortBy = $this->getSortedColumn();
+ if(!in_array($columnToSortBy, $columnsToDisplay))
+ {
+ $this->setSortedColumn('nb_visits', 'desc');
+ }
+ parent::main();
+ }
+
+ public function disableSubTableWhenShowGoals()
+ {
+ $this->controllerActionCalledWhenRequestSubTable = null;
+ }
+
+ public function setColumnsToDisplay($columnsNames)
+ {
+ $newColumnsNames = array();
+ $goals = array();
+ $idSite = $this->getIdSite();
+ if($idSite)
+ {
+ $goals = Piwik_Goals_API::getInstance()->getGoals( $idSite );
+ }
+ foreach($columnsNames as $columnName)
+ {
+ if(in_array($columnName, array('goal_%s_conversion_rate', 'goal_%s_nb_conversions', 'goal_%s_revenue_per_visit')))
+ {
+ foreach($goals as $goal)
+ {
+ $idgoal = $goal['idgoal'];
+ if($this->processOnlyIdGoal > Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_FULL_TABLE
+ && $this->processOnlyIdGoal != $idgoal)
+ {
+ continue;
+ }
+ $name = Piwik_Translate($this->getColumnTranslation($columnName), $goal['name']);
+ $columnNameGoal = str_replace('%s', $idgoal, $columnName);
+ $this->setColumnTranslation($columnNameGoal, $name);
+ if(strstr($columnNameGoal, '_rate') !== false)
+ {
+ $this->columnsToPercentageFilter[] = $columnNameGoal;
+ }
+ // For the goal table (when the flag icon is clicked), we only display the per Goal Conversion rate
+ elseif($this->processOnlyIdGoal == Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_OVERVIEW)
+ {
+ continue;
+ }
+
+ if(strstr($columnNameGoal, '_revenue') !== false)
+ {
+ $this->columnsToRevenueFilter[] = $columnNameGoal;
+ }
+ else
+ {
+ $this->columnsToConversionFilter[] = $columnNameGoal;
+ }
+ $newColumnsNames[] = $columnNameGoal;
+ }
+ }
+ else
+ {
+ $newColumnsNames[] = $columnName;
+ }
+ }
+ parent::setColumnsToDisplay($newColumnsNames);
+ }
+
+ protected function getRequestString()
+ {
+ $requestString = parent::getRequestString();
+ if($this->processOnlyIdGoal > Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_FULL_TABLE)
+ {
+ $requestString .= "&filter_only_display_idgoal=".$this->processOnlyIdGoal;
+ }
+ return $requestString . '&filter_update_columns_when_show_all_goals=1';
+ }
+
+ protected $columnsToPercentageFilter = array();
+ protected $columnsToRevenueFilter = array();
+ protected $columnsToConversionFilter = array();
+ protected $idSite = false;
+
+ private function getIdSite()
+ {
+ return $this->idSite;
+ }
+
+ protected function postDataTableLoadedFromAPI()
+ {
+ parent::postDataTableLoadedFromAPI();
+ $this->columnsToPercentageFilter[] = 'goals_conversion_rate';
+ foreach($this->columnsToPercentageFilter as $columnName)
+ {
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$rate', 'return sprintf("%.1f",$rate)."%";')));
+ }
+ $this->columnsToRevenueFilter[] = 'revenue_per_visit';
+ foreach($this->columnsToRevenueFilter as $columnName)
+ {
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$value', 'return sprintf("%.1f",$value);')));
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, array("Piwik", "getPrettyMoney"), array($this->getIdSite())));
+ }
+
+ foreach($this->columnsToConversionFilter as $columnName)
+ {
+ // this ensures that the value is set to zero for all rows where the value was not set (no conversion)
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$value', 'return $value;')));
+ }
+ }
+}
diff --git a/oss/piwik/trunk/core/ViewDataTable/Sparkline.php b/oss/piwik/trunk/core/ViewDataTable/Sparkline.php
new file mode 100644
index 00000000..071ed2bb
--- /dev/null
+++ b/oss/piwik/trunk/core/ViewDataTable/Sparkline.php
@@ -0,0 +1,105 @@
+mainAlreadyExecuted)
+ {
+ return;
+ }
+ $this->mainAlreadyExecuted = true;
+
+ // throws exception if no view access
+ $this->loadDataTableFromAPI();
+
+ $this->isDataAvailable = $this->dataTable->getRowsCount() != 0;
+ if(!$this->isDataAvailable)
+ {
+ throw new Exception(Piwik_TranslateException('General_NoDataForGraph'));
+ }
+ $values = $this->getValuesFromDataTable($this->dataTable);
+ $graph = new Piwik_Visualization_Sparkline();
+ $graph->setValues($values);
+ $graph->main();
+
+ $this->view = $graph;
+ }
+
+ protected function getValuesFromDataTableArray( $dataTableArray, $columnToPlot )
+ {
+ $dataTableArray->applyQueuedFilters();
+ $values = array();
+ foreach($dataTableArray->getArray() as $table)
+ {
+ if($table->getRowsCount() > 1)
+ {
+ throw new Exception("Expecting only one row per DataTable");
+ }
+ $value = 0;
+ $onlyRow = $table->getFirstRow();
+ if($onlyRow !== false)
+ {
+ if(!empty($columnToPlot))
+ {
+ $value = $onlyRow->getColumn($columnToPlot);
+ }
+ // if not specified, we load by default the first column found
+ // eg. case of getLastDistinctCountriesGraph
+ else
+ {
+ $columns = $onlyRow->getColumns();
+ $value = current($columns);
+ }
+ }
+ $values[] = $value;
+ }
+ return $values;
+ }
+
+ protected function getValuesFromDataTable( $dataTable )
+ {
+ $columns = $this->getColumnsToDisplay();
+ $columnToPlot = false;
+ if(!empty($columns))
+ {
+ $columnToPlot = $columns[0];
+ }
+
+ // a Piwik_DataTable_Array is returned when using the normal code path to request data from Archives, in all core plugins
+ // however plugins can also return simple datatable, hence why the sparkline can accept both data types
+ if($this->dataTable instanceof Piwik_DataTable_Array)
+ {
+ $values = $this->getValuesFromDataTableArray($dataTable, $columnToPlot);
+ }
+ elseif($this->dataTable instanceof Piwik_DataTable)
+ {
+ $values = $this->dataTable->getColumn($columnToPlot);
+ }
+ return $values;
+ }
+}
diff --git a/oss/piwik/trunk/core/Visualization/Chart.php b/oss/piwik/trunk/core/Visualization/Chart.php
new file mode 100644
index 00000000..dc3dff49
--- /dev/null
+++ b/oss/piwik/trunk/core/Visualization/Chart.php
@@ -0,0 +1,200 @@
+chart = new open_flash_chart();
+ }
+
+ public function setAxisXLabels($xLabels)
+ {
+ $this->xLabels = $xLabels;
+ }
+
+ public function setAxisXOnClick($onClick)
+ {
+ $this->xOnClick = $onClick;
+ }
+
+ public function setAxisYValues($values)
+ {
+ $this->yValues = $values;
+ }
+
+ function setAxisYUnit($yUnit)
+ {
+ if(!empty($yUnit))
+ {
+ $this->yUnit = $yUnit;
+ }
+ }
+
+ public function setAxisYLabels($labels)
+ {
+ $this->yLabels = $labels;
+ }
+
+ public function setDisplayPercentageInTooltip($bool)
+ {
+ $this->displayPercentageInTooltip = $bool;
+ }
+
+ public function setXSteps($steps)
+ {
+ $this->xSteps = $steps;
+ }
+
+ protected function getDataSetsToDisplay()
+ {
+ if(empty($this->yValues)) {
+ return false;
+ }
+ return array_keys($this->yValues);
+ }
+
+ public function getMaxValue()
+ {
+ $datasetsIds = $this->getDataSetsToDisplay();
+ if($datasetsIds === false)
+ {
+ return 0;
+ }
+ $maxCrossDataSets = false;
+ foreach($datasetsIds as $dataset)
+ {
+ $maxValue = max($this->yValues[$dataset]);
+ if($maxCrossDataSets === false
+ || $maxValue > $maxCrossDataSets)
+ {
+ $maxCrossDataSets = $maxValue;
+ }
+ }
+ if($maxCrossDataSets > 10)
+ {
+ $maxCrossDataSets = $maxCrossDataSets + 10 - $maxCrossDataSets % 10;
+ }
+ return $maxCrossDataSets;
+ }
+
+ public function setTitle($text, $css)
+ {
+ $title = new title($text);
+ $title->set_style($css);
+ $this->chart->set_title($title);
+ }
+
+ public function render()
+ {
+ if(Piwik_Url::getCurrentScheme() == 'https' ||
+ Zend_Registry::get('config')->General->reverse_proxy)
+ {
+ @header("Pragma: ");
+ @header("Cache-Control: must-revalidate");
+ }
+ return $this->chart->toPrettyString();
+ }
+
+ function customizeChartProperties()
+ {
+ $this->chart->set_number_format($num_decimals = 0,
+ $is_fixed_num_decimals_forced = true,
+ $is_decimal_separator_comma = false,
+ $is_thousand_separator_disabled = false);
+
+ $gridColour = '#E0E1E4';
+ $countValues = count($this->xLabels);
+ $this->maxValue = $this->getMaxValue();
+ $this->minValue = 0;
+
+ // X Axis
+ $this->x = new x_axis();
+ $this->x->set_colour( '#596171' );
+ $this->x->set_grid_colour( $gridColour );
+ $this->x->set_steps($this->xSteps);
+
+ // X Axis Labels
+ $this->x_labels = new x_axis_labels();
+ $this->x_labels->set_size(11);
+ //manually fix the x labels step as this doesn't work in this OFC release..
+ $xLabelsStepped = $this->xLabels;
+ foreach($xLabelsStepped as $i => &$xLabel)
+ {
+ if(($i % $this->xSteps) != 0)
+ {
+ $xLabel = '';
+ }
+ }
+ $this->x_labels->set_labels($xLabelsStepped);
+ $this->x_labels->set_steps(2);
+ $this->x->set_labels($this->x_labels);
+
+ // Y Axis
+ $this->y = new y_axis();
+ $this->y->set_colour('#ffffff');
+ $this->y->set_grid_colour($gridColour);
+ $stepsCount = 2;
+ $stepsEveryNLabel = ceil(($this->maxValue - $this->minValue) / $stepsCount);
+ if($this->maxValue == 0)
+ {
+ $this->maxValue = 1;
+ }
+ $this->y->set_range( $this->minValue, (int) ceil($this->maxValue), (int) $stepsEveryNLabel);
+ $dataSetsToDisplay = $this->getDataSetsToDisplay();
+ if($dataSetsToDisplay != false)
+ {
+ $dataSetToDisplay = current($dataSetsToDisplay);
+ $this->y->set_label_text("#val#".$this->yUnit);
+ }
+
+ // Tooltip
+ $this->tooltip = new tooltip();
+ $this->tooltip->set_shadow( true );
+ $this->tooltip->set_stroke( 1 );
+
+ // Attach elements to the graph
+ $this->chart->set_x_axis($this->x);
+ $this->chart->set_y_axis($this->y);
+ $this->chart->set_tooltip($this->tooltip);
+ $this->chart->set_bg_colour('#ffffff');
+ }
+}
diff --git a/oss/piwik/trunk/core/Visualization/Chart/Evolution.php b/oss/piwik/trunk/core/Visualization/Chart/Evolution.php
new file mode 100644
index 00000000..b4ec58fa
--- /dev/null
+++ b/oss/piwik/trunk/core/Visualization/Chart/Evolution.php
@@ -0,0 +1,92 @@
+getDataSetsToDisplay();
+ if($dataSetsToDisplay === false)
+ {
+ return;
+ }
+
+ $colors = array(
+ "0x3357A0",
+ "0xCC3399",
+ "0x9933CC",
+ "0x80a033",
+ "0xFD9816",
+ "0x246AD2",
+ "0xFD16EA",
+ "0x49C100",
+ );
+
+ $i = 0;
+ foreach($dataSetsToDisplay as $dataSetToDisplay)
+ {
+ $color = $colors[$i % count($colors)];
+
+ $labelName = $this->yLabels[$dataSetToDisplay];
+ $d = new hollow_dot();
+ $d->size(3)->halo_size(0)->colour($color);
+
+ $line = new line();
+ $line->set_default_dot_style($d);
+ $line->set_key($labelName, 11);
+ $line->set_width( 1 );
+ $line->set_colour( $color );
+
+ // Line Values
+ // Note: we have to manually create the dot values as the steps feature doens't work on X axis
+ // when it's working again, we can remove code below and set generic tooltip above: // ->tooltip('#x_label#
#val# '.$labelName)
+ $yValues = $this->yValues[$dataSetToDisplay];
+ $labelName = $this->yLabels[$dataSetToDisplay];
+ $lineValues = array();
+ $j = 0;
+ foreach($this->xLabels as $label) {
+ $value = (float)$yValues[$j];
+ $lineValue = new hollow_dot($value);
+
+ $unit = $this->yUnit;
+ $lineValue->tooltip("$label
$value$unit $labelName");
+ if(!empty($this->xOnClick))
+ {
+ $lineValue->on_click("piwikHelper.redirectToUrl('".$this->xOnClick[$j]."')");
+ }
+ $lineValues[] = $lineValue;
+ $j++;
+ }
+ $line->set_values( $lineValues );
+ $lines[] = $line;
+ $i++;
+ }
+ foreach($lines as $line)
+ {
+ $this->chart->add_element($line);
+ }
+ // if one column is a percentage we set the grid accordingly
+ // note: it is invalid to plot a percentage dataset along with a numeric dataset
+ if($this->yUnit == '%'
+ && $this->maxValue > 90)
+ {
+ $this->y->set_range( 0, 100, 50);
+ }
+ }
+}
diff --git a/oss/piwik/trunk/core/Visualization/Chart/Pie.php b/oss/piwik/trunk/core/Visualization/Chart/Pie.php
new file mode 100644
index 00000000..699a6221
--- /dev/null
+++ b/oss/piwik/trunk/core/Visualization/Chart/Pie.php
@@ -0,0 +1,73 @@
+getDataSetsToDisplay();
+ if($dataSetsToDisplay === false)
+ {
+ return;
+ }
+ $dataSetToDisplay = current($dataSetsToDisplay);
+
+ // create the Pie
+ $pie = new pie();
+ $pie->set_alpha("0.6");
+ $pie->set_start_angle( 35 );
+ $pie->add_animation( new pie_fade() );
+ $pie->set_label_colour('#142448');
+ $pie->set_colours( array('#3C5A69','#679BB5','#695A3C','#B58E67','#969696') );
+
+ // create the Pie values
+ $yValues = $this->yValues[$dataSetToDisplay];
+ $labelName = $this->yLabels[$dataSetToDisplay];
+ $unit = $this->yUnit;
+ $sum = array_sum($yValues);
+ $pieValues = array();
+ $i = 0;
+ foreach($this->xLabels as $label) {
+ $value = (float)$yValues[$i];
+ $i++;
+ // we never plot empty pie slices (eg. visits by server time pie chart)
+ if($value <= 0) {
+ continue;
+ }
+ $pieValue = new pie_value($value, $label);
+ $percentage = round(100 * $value / $sum);
+ $pieValue->set_tooltip("$label
$percentage% ($value$unit $labelName)");
+ $pieValues[] = $pieValue;
+ }
+ $pie->set_values($pieValues);
+
+ $this->chart->add_element($pie);
+ }
+}
diff --git a/oss/piwik/trunk/core/Visualization/Chart/VerticalBar.php b/oss/piwik/trunk/core/Visualization/Chart/VerticalBar.php
new file mode 100644
index 00000000..61302a02
--- /dev/null
+++ b/oss/piwik/trunk/core/Visualization/Chart/VerticalBar.php
@@ -0,0 +1,76 @@
+getDataSetsToDisplay();
+ if($dataSetsToDisplay === false)
+ {
+ return;
+ }
+ $dataSetToDisplay = current($dataSetsToDisplay);
+
+ $this->x->set_grid_colour('#ffffff');
+ $this->x_labels->set_steps(2);
+ $this->x->set_stroke(1);
+
+ // create the Bar object
+ $bar = new bar_filled('#3B5AA9', '#063E7E');
+ $bar->set_alpha("0.5");
+ $bar->set_key($this->yLabels[$dataSetToDisplay], 12);
+ $bar->set_tooltip( '#val# #key#');
+
+ // create the bar values
+ $yValues = $this->yValues[$dataSetToDisplay];
+ $labelName = $this->yLabels[$dataSetToDisplay];
+ $unit = $this->yUnit;
+ $barValues = array();
+ $i = 0;
+ $sum = array_sum($yValues);
+ foreach($this->xLabels as $label) {
+ $value = (float)$yValues[$i];
+
+ $displayPercentage = '';
+ if($this->displayPercentageInTooltip)
+ {
+ $percentage = round(100 * $value / $sum);
+ $displayPercentage = "($percentage%)";
+ }
+ $barValue = new bar_value($value);
+ $barValue->set_tooltip("$label
$value$unit $labelName $displayPercentage");
+ $barValues[] = $barValue;
+ $i++;
+ }
+ $bar->set_values($barValues);
+ $this->chart->add_element($bar);
+ }
+}
diff --git a/oss/piwik/trunk/core/Visualization/Cloud.php b/oss/piwik/trunk/core/Visualization/Cloud.php
new file mode 100644
index 00000000..9d525b48
--- /dev/null
+++ b/oss/piwik/trunk/core/Visualization/Cloud.php
@@ -0,0 +1,108 @@
+wordsArray[$word]))
+ {
+ $this->wordsArray[$word] += $value;
+ }
+ else
+ {
+ $this->wordsArray[$word] = $value;
+ }
+ }
+
+ public function render()
+ {
+ $this->shuffleCloud();
+ $return = array();
+ if(empty($this->wordsArray)) {
+ return array();
+ }
+ $maxValue = max($this->wordsArray);
+ foreach ($this->wordsArray as $word => $popularity)
+ {
+ $wordTruncated = $word;
+ if(strlen($word) > $this->truncatingLimit)
+ {
+ $wordTruncated = substr($word, 0, $this->truncatingLimit - 3).'...';
+ }
+ $percent = ($popularity / $maxValue) * 100;
+ // CSS style value
+ $sizeRange = $this->getClassFromPercent($percent);
+
+ $return[$word] = array(
+ 'word' => $word,
+ 'wordTruncated' => $wordTruncated,
+ 'value' => $popularity,
+ 'size' => $sizeRange,
+ 'percent' => $percent,
+ );
+ }
+ return $return;
+ }
+
+ /*
+ * Shuffle associated names in array
+ */
+ protected function shuffleCloud()
+ {
+ $keys = array_keys($this->wordsArray);
+
+ shuffle($keys);
+
+ if (count($keys) && is_array($keys))
+ {
+ $tmpArray = $this->wordsArray;
+ $this->wordsArray = array();
+ foreach ($keys as $key => $value)
+ $this->wordsArray[$value] = $tmpArray[$value];
+ }
+ }
+
+ /*
+ * Get the class range using a percentage
+ *
+ * @returns int $class
+ */
+ protected function getClassFromPercent($percent)
+ {
+ $mapping = array(95, 70, 50, 30, 15, 5, 0);
+ foreach($mapping as $key => $value)
+ {
+ if($percent >= $value)
+ {
+ return $key;
+ }
+ }
+ }
+}
diff --git a/oss/piwik/trunk/core/Visualization/Sparkline.php b/oss/piwik/trunk/core/Visualization/Sparkline.php
new file mode 100644
index 00000000..dcc902e2
--- /dev/null
+++ b/oss/piwik/trunk/core/Visualization/Sparkline.php
@@ -0,0 +1,99 @@
+values = $data;
+ }
+
+ static public function getWidth()
+ {
+ return 100;
+ }
+
+ static public function getHeight()
+ {
+ return 25;
+ }
+
+ function main()
+ {
+ $width = self::getWidth();
+ $height = self::getHeight();
+
+ $sparkline = new Sparkline_Line();
+ $sparkline->SetColor('lineColor', 22, 44, 74); // dark blue
+ $sparkline->SetColorHtml('red', '#FF7F7F');
+ $sparkline->SetColorHtml('blue', '#55AAFF');
+ $sparkline->SetColorHtml('green', '#75BF7C');
+
+ $min = $max = $last = null;
+ $i = 0;
+ foreach($this->values as $value)
+ {
+ // 50% should be plotted as 50
+ $toRemove = '%';
+ if(strpos($value, $toRemove) !== false)
+ {
+ $value = str_replace($toRemove, '', $value);
+ }
+ $sparkline->SetData($i, $value);
+ if( null == $min || $value <= $min[1])
+ {
+ $min = array($i, $value);
+ }
+ if(null == $max || $value >= $max[1])
+ {
+ $max = array($i, $value);
+ }
+ $last = array($i, $value);
+ $i++;
+ }
+ $sparkline->SetYMin(0);
+ $sparkline->SetYMax($max[1]);
+ $sparkline->SetPadding( 3, 0, 2, 0 ); // top, right, bottom, left
+// $font = FONT_2;
+ $sparkline->SetFeaturePoint($min[0], $min[1], 'red', 5);
+ $sparkline->SetFeaturePoint($max[0], $max[1], 'green', 5);
+ $sparkline->SetFeaturePoint($last[0], $last[1], 'blue', 5);
+ $sparkline->SetLineSize(3); // for renderresampled, linesize is on virtual image
+ $ratio = 1;
+// var_dump($min);var_dump($max);var_dump($lasts);exit;
+ $sparkline->RenderResampled($width*$ratio, $height*$ratio);
+
+ $this->sparkline = $sparkline;
+ }
+
+ function render()
+ {
+ $this->sparkline->Output();
+ }
+}