PDF rausgenommen
This commit is contained in:
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations;
|
||||
|
||||
use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/JqplotDataGenerator.php';
|
||||
|
||||
/**
|
||||
* This plugin contains all core visualizations, such as the normal HTML table and
|
||||
* jqPlot graphs.
|
||||
*/
|
||||
class CoreVisualizations extends \Piwik\Plugin
|
||||
{
|
||||
/**
|
||||
* @see \Piwik\Plugin::registerEvents
|
||||
*/
|
||||
public function registerEvents()
|
||||
{
|
||||
return array(
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
|
||||
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
|
||||
'UsersManager.deleteUser' => 'deleteUser',
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteUser($userLogin)
|
||||
{
|
||||
ViewDataTableManager::clearUserViewDataTableParameters($userLogin);
|
||||
}
|
||||
|
||||
public function getStylesheetFiles(&$stylesheets)
|
||||
{
|
||||
$stylesheets[] = "plugins/CoreVisualizations/angularjs/series-picker/series-picker.component.less";
|
||||
$stylesheets[] = "plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.less";
|
||||
|
||||
$stylesheets[] = "plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less";
|
||||
$stylesheets[] = "plugins/CoreVisualizations/stylesheets/jqplot.css";
|
||||
}
|
||||
|
||||
public function getJsFiles(&$jsFiles)
|
||||
{
|
||||
$jsFiles[] = "plugins/CoreVisualizations/angularjs/series-picker/series-picker.component.js";
|
||||
$jsFiles[] = "plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.js";
|
||||
|
||||
$jsFiles[] = "plugins/CoreVisualizations/javascripts/seriesPicker.js";
|
||||
$jsFiles[] = "plugins/CoreVisualizations/javascripts/jqplot.js";
|
||||
$jsFiles[] = "plugins/CoreVisualizations/javascripts/jqplotBarGraph.js";
|
||||
$jsFiles[] = "plugins/CoreVisualizations/javascripts/jqplotPieGraph.js";
|
||||
$jsFiles[] = "plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js";
|
||||
}
|
||||
|
||||
public function getClientSideTranslationKeys(&$translationKeys)
|
||||
{
|
||||
$translationKeys[] = 'General_MetricsToPlot';
|
||||
$translationKeys[] = 'General_MetricToPlot';
|
||||
$translationKeys[] = 'General_RecordsToPlot';
|
||||
$translationKeys[] = 'General_SaveImageOnYourComputer';
|
||||
$translationKeys[] = 'General_ExportAsImage';
|
||||
$translationKeys[] = 'General_NoDataForGraph';
|
||||
$translationKeys[] = 'General_EvolutionSummaryGeneric';
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator\Chart;
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php';
|
||||
|
||||
/**
|
||||
* Generates JSON data used to configure and populate JQPlot graphs.
|
||||
*
|
||||
* Supports pie graphs, bar graphs and time serieses (aka, evolution graphs).
|
||||
*/
|
||||
class JqplotDataGenerator
|
||||
{
|
||||
/**
|
||||
* View properties. @see Piwik\ViewDataTable for more info.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $properties;
|
||||
|
||||
protected $graphType;
|
||||
|
||||
/**
|
||||
* Creates a new JqplotDataGenerator instance for a graph type and view properties.
|
||||
*
|
||||
* @param string $type 'pie', 'bar', or 'evolution'
|
||||
* @param array $properties The view properties.
|
||||
* @throws \Exception
|
||||
* @return JqplotDataGenerator
|
||||
*/
|
||||
public static function factory($type, $properties)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'evolution':
|
||||
return new JqplotDataGenerator\Evolution($properties, $type);
|
||||
case 'pie':
|
||||
case 'bar':
|
||||
return new JqplotDataGenerator($properties, $type);
|
||||
default:
|
||||
throw new Exception("Unknown JqplotDataGenerator type '$type'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $properties
|
||||
* @param string $graphType
|
||||
*
|
||||
* @internal param \Piwik\Plugin\ViewDataTable $visualization
|
||||
*/
|
||||
public function __construct($properties, $graphType)
|
||||
{
|
||||
$this->properties = $properties;
|
||||
$this->graphType = $graphType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JSON graph data and returns it.
|
||||
*
|
||||
* @param DataTable|DataTable\Map $dataTable
|
||||
* @return string
|
||||
*/
|
||||
public function generate($dataTable)
|
||||
{
|
||||
$visualization = new Chart();
|
||||
|
||||
if ($dataTable->getRowsCount() > 0) {
|
||||
// if addTotalRow was called in GenerateGraphHTML, add a row containing totals of
|
||||
// different metrics
|
||||
if ($this->properties['add_total_row']) {
|
||||
$dataTable->queueFilter('AddSummaryRow', Piwik::translate('General_Total'));
|
||||
}
|
||||
|
||||
$dataTable->applyQueuedFilters();
|
||||
$this->initChartObjectData($dataTable, $visualization);
|
||||
}
|
||||
|
||||
return $visualization->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable|DataTable\Map $dataTable
|
||||
* @param $visualization
|
||||
*/
|
||||
protected function initChartObjectData($dataTable, $visualization)
|
||||
{
|
||||
$xLabels = $dataTable->getColumn('label');
|
||||
|
||||
$columnNames = $this->properties['columns_to_display'];
|
||||
if (($labelColumnIndex = array_search('label', $columnNames)) !== false) {
|
||||
unset($columnNames[$labelColumnIndex]);
|
||||
}
|
||||
|
||||
$columnNameToTranslation = $columnNameToValue = array();
|
||||
foreach ($columnNames as $columnName) {
|
||||
$columnNameToTranslation[$columnName] = @$this->properties['translations'][$columnName];
|
||||
|
||||
$columnNameToValue[$columnName] = $dataTable->getColumn($columnName);
|
||||
}
|
||||
|
||||
$visualization->dataTable = $dataTable;
|
||||
$visualization->properties = $this->properties;
|
||||
|
||||
$visualization->setAxisXLabels($xLabels);
|
||||
$visualization->setAxisYValues($columnNameToValue);
|
||||
$visualization->setAxisYLabels($columnNameToTranslation);
|
||||
|
||||
$units = $this->getUnitsForColumnsToDisplay();
|
||||
$visualization->setAxisYUnits($units);
|
||||
}
|
||||
|
||||
protected function getUnitsForColumnsToDisplay()
|
||||
{
|
||||
// derive units from column names
|
||||
$units = $this->deriveUnitsFromRequestedColumnNames();
|
||||
if (!empty($this->properties['y_axis_unit'])) {
|
||||
$units = array_fill(0, count($units), $this->properties['y_axis_unit']);
|
||||
}
|
||||
|
||||
// the bar charts contain the labels a first series
|
||||
// this series has to be removed from the units
|
||||
reset($units);
|
||||
if ($this->graphType == 'bar'
|
||||
&& key($units) == 'label'
|
||||
) {
|
||||
array_shift($units);
|
||||
}
|
||||
|
||||
return $units;
|
||||
}
|
||||
|
||||
private function deriveUnitsFromRequestedColumnNames()
|
||||
{
|
||||
$idSite = Common::getRequestVar('idSite', null, 'int');
|
||||
|
||||
$units = array();
|
||||
foreach ($this->properties['columns_to_display'] as $columnName) {
|
||||
$derivedUnit = Metrics::getUnit($columnName, $idSite);
|
||||
$units[$columnName] = empty($derivedUnit) ? false : $derivedUnit;
|
||||
}
|
||||
return $units;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\ProxyHttp;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Chart
|
||||
{
|
||||
// the data kept here conforms to the jqplot data layout
|
||||
// @see http://www.jqplot.com/docs/files/jqPlotOptions-txt.html
|
||||
protected $series = array();
|
||||
protected $data = array();
|
||||
protected $axes = array();
|
||||
|
||||
// temporary
|
||||
public $properties;
|
||||
|
||||
public function setAxisXLabels($xLabels)
|
||||
{
|
||||
$xSteps = $this->properties['x_axis_step_size'];
|
||||
$showAllTicks = $this->properties['show_all_ticks'];
|
||||
|
||||
$this->axes['xaxis']['labels'] = array_values($xLabels);
|
||||
|
||||
$ticks = array_values($xLabels);
|
||||
|
||||
if (!$showAllTicks) {
|
||||
// unset labels so there are $xSteps number of blank ticks between labels
|
||||
foreach ($ticks as $i => &$label) {
|
||||
if ($i % $xSteps != 0) {
|
||||
$label = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->axes['xaxis']['ticks'] = $ticks;
|
||||
}
|
||||
|
||||
public function setAxisXOnClick(&$onClick)
|
||||
{
|
||||
$this->axes['xaxis']['onclick'] = & $onClick;
|
||||
}
|
||||
|
||||
public function setAxisYValues(&$values)
|
||||
{
|
||||
foreach ($values as $label => &$data) {
|
||||
$this->series[] = array(
|
||||
'label' => $label,
|
||||
'internalLabel' => $label
|
||||
);
|
||||
|
||||
array_walk($data, function (&$v) {
|
||||
$v = (float) Common::forceDotAsSeparatorForDecimalPoint($v);
|
||||
});
|
||||
$this->data[] = & $data;
|
||||
}
|
||||
}
|
||||
|
||||
public function setAxisYUnits($yUnits)
|
||||
{
|
||||
$yUnits = array_values(array_map('strval', $yUnits));
|
||||
|
||||
// generate axis IDs for each unique y unit
|
||||
$axesIds = array();
|
||||
foreach ($yUnits as $idx => $unit) {
|
||||
if (!isset($axesIds[$unit])) {
|
||||
// handle axes ids: first y[]axis, then y[2]axis, y[3]axis...
|
||||
$nextAxisId = empty($axesIds) ? '' : count($axesIds) + 1;
|
||||
|
||||
$axesIds[$unit] = 'y' . $nextAxisId . 'axis';
|
||||
}
|
||||
}
|
||||
|
||||
// generate jqplot axes config
|
||||
foreach ($axesIds as $unit => $axisId) {
|
||||
$this->axes[$axisId]['tickOptions']['formatString'] = '%s' . $unit;
|
||||
}
|
||||
|
||||
// map each series to appropriate yaxis
|
||||
foreach ($yUnits as $idx => $unit) {
|
||||
$this->series[$idx]['yaxis'] = $axesIds[$unit];
|
||||
}
|
||||
}
|
||||
|
||||
public function setAxisYLabels($labels)
|
||||
{
|
||||
foreach ($this->series as &$series) {
|
||||
$label = $series['internalLabel'];
|
||||
if (isset($labels[$label])) {
|
||||
$series['label'] = $labels[$label];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
ProxyHttp::overrideCacheControlHeaders();
|
||||
|
||||
// See http://www.jqplot.com/docs/files/jqPlotOptions-txt.html
|
||||
$data = array(
|
||||
'params' => array(
|
||||
'axes' => &$this->axes,
|
||||
'series' => &$this->series
|
||||
),
|
||||
'data' => &$this->data
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
|
||||
use Piwik\Archive\DataTableFactory;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
use Piwik\Url;
|
||||
|
||||
/**
|
||||
* Generates JQPlot JSON data/config for evolution graphs.
|
||||
*/
|
||||
class Evolution extends JqplotDataGenerator
|
||||
{
|
||||
/**
|
||||
* @param DataTable|DataTable\Map $dataTable
|
||||
* @param Chart $visualization
|
||||
*/
|
||||
protected function initChartObjectData($dataTable, $visualization)
|
||||
{
|
||||
// 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 Set
|
||||
|
||||
if ($dataTable instanceof DataTable) {
|
||||
parent::initChartObjectData($dataTable, $visualization);
|
||||
return;
|
||||
}
|
||||
|
||||
// the X label is extracted from the 'period' object in the table's metadata
|
||||
$xLabels = array();
|
||||
foreach ($dataTable->getDataTables() as $metadataDataTable) {
|
||||
$xLabels[] = $metadataDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLocalizedShortString(); // eg. "Aug 2009"
|
||||
}
|
||||
|
||||
$units = $this->getUnitsForColumnsToDisplay();
|
||||
|
||||
// if rows to display are not specified, default to all rows (TODO: perhaps this should be done elsewhere?)
|
||||
$rowsToDisplay = $this->properties['rows_to_display']
|
||||
? : array_unique($dataTable->getColumn('label'))
|
||||
? : array(false) // make sure that a series is plotted even if there is no data
|
||||
;
|
||||
|
||||
// collect series data to show. each row-to-display/column-to-display permutation creates a series.
|
||||
$allSeriesData = array();
|
||||
$seriesUnits = array();
|
||||
foreach ($rowsToDisplay as $rowLabel) {
|
||||
foreach ($this->properties['columns_to_display'] as $columnName) {
|
||||
$seriesLabel = $this->getSeriesLabel($rowLabel, $columnName);
|
||||
$seriesData = $this->getSeriesData($rowLabel, $columnName, $dataTable);
|
||||
|
||||
$allSeriesData[$seriesLabel] = $seriesData;
|
||||
$seriesUnits[$seriesLabel] = $units[$columnName];
|
||||
}
|
||||
}
|
||||
|
||||
$visualization->dataTable = $dataTable;
|
||||
$visualization->properties = $this->properties;
|
||||
|
||||
$visualization->setAxisXLabels($xLabels);
|
||||
$visualization->setAxisYValues($allSeriesData);
|
||||
$visualization->setAxisYUnits($seriesUnits);
|
||||
|
||||
$dataTables = $dataTable->getDataTables();
|
||||
|
||||
if ($this->isLinkEnabled()) {
|
||||
$idSite = Common::getRequestVar('idSite', null, 'int');
|
||||
$periodLabel = reset($dataTables)->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLabel();
|
||||
|
||||
$axisXOnClick = array();
|
||||
foreach ($dataTable->getDataTables() as $metadataDataTable) {
|
||||
$dateInUrl = $metadataDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getDateStart();
|
||||
$parameters = array(
|
||||
'idSite' => $idSite,
|
||||
'period' => $periodLabel,
|
||||
'date' => $dateInUrl->toString(),
|
||||
'segment' => \Piwik\API\Request::getRawSegmentFromRequest()
|
||||
);
|
||||
$link = Url::getQueryStringFromParameters($parameters);
|
||||
$axisXOnClick[] = $link;
|
||||
}
|
||||
$visualization->setAxisXOnClick($axisXOnClick);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSeriesData($rowLabel, $columnName, DataTable\Map $dataTable)
|
||||
{
|
||||
$seriesData = array();
|
||||
foreach ($dataTable->getDataTables() as $childTable) {
|
||||
// get the row for this label (use the first if $rowLabel is false)
|
||||
if ($rowLabel === false) {
|
||||
$row = $childTable->getFirstRow();
|
||||
} else {
|
||||
$row = $childTable->getRowFromLabel($rowLabel);
|
||||
}
|
||||
|
||||
// get series data point. defaults to 0 if no row or no column value.
|
||||
if ($row === false) {
|
||||
$seriesData[] = 0;
|
||||
} else {
|
||||
$seriesData[] = $row->getColumn($columnName) ? : 0;
|
||||
}
|
||||
}
|
||||
return $seriesData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the series label from the row label and the column name.
|
||||
* If the row label is set, both the label and the column name are displayed.
|
||||
* @param string $rowLabel
|
||||
* @param string $columnName
|
||||
* @return string
|
||||
*/
|
||||
private function getSeriesLabel($rowLabel, $columnName)
|
||||
{
|
||||
$metricLabel = @$this->properties['translations'][$columnName];
|
||||
|
||||
if ($rowLabel !== false) {
|
||||
// eg. "Yahoo! (Visits)"
|
||||
$label = "$rowLabel ($metricLabel)";
|
||||
} else {
|
||||
// eg. "Visits"
|
||||
$label = $metricLabel;
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function isLinkEnabled()
|
||||
{
|
||||
static $linkEnabled;
|
||||
if (!isset($linkEnabled)) {
|
||||
// 1) Custom Date Range always have link disabled, otherwise
|
||||
// the graph data set is way too big and fails to display
|
||||
// 2) disableLink parameter is set in the Widgetize "embed" code
|
||||
$linkEnabled = !Common::getRequestVar('disableLink', 0, 'int')
|
||||
&& Common::getRequestVar('period', 'day') != 'range';
|
||||
}
|
||||
return $linkEnabled;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Metrics\Formatter;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Metrics\Formatter;
|
||||
|
||||
/**
|
||||
* A metrics formatter that prettifies metric values without returning string values.
|
||||
* Results of this class can be converted to numeric values and processed further in
|
||||
* some way.
|
||||
*/
|
||||
class Numeric extends Formatter
|
||||
{
|
||||
public function getPrettyNumber($value, $precision = 0)
|
||||
{
|
||||
return round($value, $precision);
|
||||
}
|
||||
|
||||
public function getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence = false, $round = false)
|
||||
{
|
||||
return $round ? (int)$numberOfSeconds : (float) Common::forceDotAsSeparatorForDecimalPoint($numberOfSeconds);
|
||||
}
|
||||
|
||||
public function getPrettySizeFromBytes($size, $unit = null, $precision = 1)
|
||||
{
|
||||
list($size, $sizeUnit) = $this->getPrettySizeFromBytesWithUnit($size, $unit, $precision);
|
||||
return $size;
|
||||
}
|
||||
|
||||
public function getPrettyMoney($value, $idSite)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getPrettyPercentFromQuotient($value)
|
||||
{
|
||||
return $value * 100;
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Plugin\Visualization;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* Generates a tag cloud from a given data array.
|
||||
* The generated tag cloud can be in PHP format, or in HTML.
|
||||
*
|
||||
* Inspired from Derek Harvey (www.derekharvey.co.uk)
|
||||
*
|
||||
* @property Cloud\Config $config
|
||||
*
|
||||
*/
|
||||
class Cloud extends Visualization
|
||||
{
|
||||
const ID = 'cloud';
|
||||
const TEMPLATE_FILE = "@CoreVisualizations/_dataTableViz_tagCloud.twig";
|
||||
const FOOTER_ICON = 'icon-tag-cloud';
|
||||
const FOOTER_ICON_TITLE = 'General_TagCloud';
|
||||
|
||||
/** Used by system tests to make sure output is consistent. */
|
||||
public static $debugDisableShuffle = false;
|
||||
public $truncatingLimit = 50;
|
||||
|
||||
protected $wordsArray = array();
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new Cloud\Config();
|
||||
}
|
||||
|
||||
public function beforeRender()
|
||||
{
|
||||
$this->config->show_exclude_low_population = false;
|
||||
$this->config->show_offset_information = false;
|
||||
$this->config->show_limit_control = false;
|
||||
}
|
||||
|
||||
public function afterAllFiltersAreApplied()
|
||||
{
|
||||
if ($this->dataTable->getRowsCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$columnToDisplay = isset($this->config->columns_to_display[1]) ? $this->config->columns_to_display[1] : 'nb_visits';
|
||||
$labelMetadata = array();
|
||||
|
||||
foreach ($this->dataTable->getRows() as $row) {
|
||||
$logo = false;
|
||||
if ($this->config->display_logo_instead_of_label) {
|
||||
$logo = $row->getMetadata('logo');
|
||||
}
|
||||
|
||||
$label = $row->getColumn('label');
|
||||
|
||||
$labelMetadata[$label] = array(
|
||||
'logo' => $logo,
|
||||
'url' => $row->getMetadata('url'),
|
||||
);
|
||||
|
||||
$this->addWord($label, $row->getColumn($columnToDisplay));
|
||||
}
|
||||
|
||||
$cloudValues = $this->getCloudValues();
|
||||
foreach ($cloudValues as &$value) {
|
||||
$value['logoWidth'] = round(max(16, $value['percent']));
|
||||
}
|
||||
|
||||
$this->assignTemplateVar('labelMetadata', $labelMetadata);
|
||||
$this->assignTemplateVar('cloudColumn', $columnToDisplay);
|
||||
$this->assignTemplateVar('cloudValues', $cloudValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign word to array
|
||||
* @param string $word
|
||||
* @param int $value
|
||||
* @return string
|
||||
*/
|
||||
public function addWord($word, $value = 1)
|
||||
{
|
||||
if (isset($this->wordsArray[$word])) {
|
||||
$this->wordsArray[$word] += $value;
|
||||
} else {
|
||||
$this->wordsArray[$word] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCloudValues()
|
||||
{
|
||||
$this->shuffleCloud();
|
||||
|
||||
if (empty($this->wordsArray)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$return = array();
|
||||
$maxValue = max($this->wordsArray);
|
||||
|
||||
foreach ($this->wordsArray as $word => $popularity) {
|
||||
|
||||
$wordTruncated = $this->truncateWordIfNeeded($word);
|
||||
$percent = $this->getPercentage($popularity, $maxValue);
|
||||
$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()
|
||||
{
|
||||
if (self::$debugDisableShuffle) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = array_keys($this->wordsArray);
|
||||
|
||||
shuffle($keys);
|
||||
|
||||
if (count($keys) && is_array($keys)) {
|
||||
|
||||
$tmpArray = $this->wordsArray;
|
||||
|
||||
$this->wordsArray = array();
|
||||
foreach ($keys as $value) {
|
||||
$this->wordsArray[$value] = $tmpArray[$value];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class range using a percentage
|
||||
*
|
||||
* @param $percent
|
||||
*
|
||||
* @return 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;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $word
|
||||
* @return string
|
||||
*/
|
||||
private function truncateWordIfNeeded($word)
|
||||
{
|
||||
$word = Common::unsanitizeInputValue($word);
|
||||
|
||||
if (Common::mb_strlen($word) > $this->truncatingLimit) {
|
||||
return Common::mb_substr($word, 0, $this->truncatingLimit - 3) . '...';
|
||||
}
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
private function getPercentage($popularity, $maxValue)
|
||||
{
|
||||
// case hideFutureHoursWhenToday=1 shows hours with no visits
|
||||
if ($maxValue == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$percent = ($popularity / $maxValue) * 100;
|
||||
|
||||
return $percent;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\Cloud;
|
||||
|
||||
use Piwik\ViewDataTable\Config as VisualizationConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class Config extends VisualizationConfig
|
||||
{
|
||||
|
||||
/**
|
||||
* Whether to display the logo assocatied with a DataTable row (stored as 'logo' row metadata)
|
||||
* instead of the label in Tag Clouds.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $display_logo_instead_of_label = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array('display_logo_instead_of_label'));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\Plugins\CoreVisualizations\Metrics\Formatter\Numeric;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Visualization;
|
||||
|
||||
/**
|
||||
* This is an abstract visualization that should be the base of any 'graph' visualization.
|
||||
* This class defines certain visualization properties that are specific to all graph types.
|
||||
* Derived visualizations can decide for themselves whether they should support individual
|
||||
* properties.
|
||||
*
|
||||
* @property Graph\Config $config
|
||||
*/
|
||||
abstract class Graph extends Visualization
|
||||
{
|
||||
const ID = 'graph';
|
||||
|
||||
public $selectableRows = array();
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new Graph\Config();
|
||||
}
|
||||
|
||||
public static function getDefaultRequestConfig()
|
||||
{
|
||||
$config = parent::getDefaultRequestConfig();
|
||||
$config->addPropertiesThatShouldBeAvailableClientSide(array('columns'));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function beforeRender()
|
||||
{
|
||||
if ($this->config->show_goals) {
|
||||
$this->config->translations['nb_conversions'] = Piwik::translate('Goals_ColumnConversions');
|
||||
$this->config->translations['revenue'] = Piwik::translate('General_TotalRevenue');
|
||||
}
|
||||
}
|
||||
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
// TODO: this should not be required here. filter_limit should not be a view property, instead HtmlTable should use 'limit' or something,
|
||||
// and manually set request_parameters_to_modify['filter_limit'] based on that. (same for filter_offset).
|
||||
$this->requestConfig->request_parameters_to_modify['filter_limit'] = false;
|
||||
|
||||
if ($this->config->max_graph_elements) {
|
||||
$this->requestConfig->request_parameters_to_modify['filter_truncate'] = $this->config->max_graph_elements - 1;
|
||||
}
|
||||
|
||||
$this->requestConfig->request_parameters_to_modify['format_metrics'] = 1;
|
||||
|
||||
$this->metricsFormatter = new Numeric();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what rows are selectable and stores them in the selectable_rows property in
|
||||
* a format the SeriesPicker JavaScript class can use.
|
||||
*/
|
||||
public function determineWhichRowsAreSelectable()
|
||||
{
|
||||
if ($this->config->row_picker_match_rows_by === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// collect all selectable rows
|
||||
$self = $this;
|
||||
|
||||
$this->dataTable->filter(function ($dataTable) use ($self) {
|
||||
/** @var DataTable $dataTable */
|
||||
|
||||
foreach ($dataTable->getRows() as $row) {
|
||||
$rowLabel = $row->getColumn('label');
|
||||
|
||||
if (false === $rowLabel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// build config
|
||||
if (!isset($self->selectableRows[$rowLabel])) {
|
||||
$self->selectableRows[$rowLabel] = array(
|
||||
'label' => $rowLabel,
|
||||
'matcher' => $rowLabel,
|
||||
'displayed' => $self->isRowVisible($rowLabel)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function isRowVisible($rowLabel)
|
||||
{
|
||||
$isVisible = true;
|
||||
if ('label' == $this->config->row_picker_match_rows_by) {
|
||||
$isVisible = in_array($rowLabel, $this->config->rows_to_display === false ? [] : $this->config->rows_to_display);
|
||||
}
|
||||
|
||||
return $isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults the selectable_columns property if it has not been set and then transforms
|
||||
* it into something the SeriesPicker JavaScript class can use.
|
||||
*/
|
||||
public function afterAllFiltersAreApplied()
|
||||
{
|
||||
$this->determineWhichRowsAreSelectable();
|
||||
|
||||
$this->config->selectable_rows = array_values($this->selectableRows);
|
||||
|
||||
if ($this->config->add_total_row) {
|
||||
$totalTranslation = Piwik::translate('General_Total');
|
||||
$this->config->selectable_rows[] = array(
|
||||
'label' => $totalTranslation,
|
||||
'matcher' => $totalTranslation,
|
||||
'displayed' => $this->isRowVisible($totalTranslation)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->config->show_goals) {
|
||||
$this->config->addTranslations(array(
|
||||
'nb_conversions' => Piwik::translate('Goals_ColumnConversions'),
|
||||
'revenue' => Piwik::translate('General_TotalRevenue')
|
||||
));
|
||||
}
|
||||
|
||||
// set default selectable columns, if none specified
|
||||
$selectableColumns = $this->config->selectable_columns;
|
||||
if (false === $selectableColumns) {
|
||||
$selectableColumns = array('nb_visits', 'nb_actions', 'nb_uniq_visitors', 'nb_users');
|
||||
|
||||
if ($this->config->show_goals) {
|
||||
$goalMetrics = array('nb_conversions', 'revenue');
|
||||
$selectableColumns = array_merge($selectableColumns, $goalMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
$transformed = array();
|
||||
foreach ($selectableColumns as $column) {
|
||||
$transformed[] = array(
|
||||
'column' => $column,
|
||||
'translation' => @$this->config->translations[$column],
|
||||
'displayed' => in_array($column, $this->config->columns_to_display)
|
||||
);
|
||||
}
|
||||
|
||||
$this->config->selectable_columns = $transformed;
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\Graph;
|
||||
|
||||
use Piwik\ViewDataTable\Config as VisualizationConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class Config extends VisualizationConfig
|
||||
{
|
||||
|
||||
/**
|
||||
* Whether the series picker should allow picking more than one series or not.
|
||||
*
|
||||
* Default value: true
|
||||
*/
|
||||
public $allow_multi_select_series_picker = true;
|
||||
|
||||
/**
|
||||
* The maximum number of rows to render. All other rows will be aggregated in an 'Others' row.
|
||||
*
|
||||
* Default value: false (no limit)
|
||||
*/
|
||||
public $max_graph_elements = false;
|
||||
|
||||
/**
|
||||
* Array property that contains the names of columns that can be selected in the Series Picker.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $selectable_columns = false;
|
||||
|
||||
/**
|
||||
* Contains the column (if any) of the values used in the Row Picker.
|
||||
*
|
||||
* @see self::ROWS_TO_DISPLAY
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $row_picker_match_rows_by = false;
|
||||
|
||||
/**
|
||||
* Contains the list of values identifying rows that should be displayed as separate series.
|
||||
* The values are of a specific column determined by the row_picker_match_rows_by column.
|
||||
*
|
||||
* @see self::ROW_PICKER_VALUE_COLUMN
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $rows_to_display = false;
|
||||
|
||||
/**
|
||||
* Contains the list of values available for the Row Picker. Currently set to be all visible
|
||||
* rows, if the row_picker_match_rows_by property is set.
|
||||
*
|
||||
* @see self::ROW_PICKER_VALUE_COLUMN
|
||||
*/
|
||||
public $selectable_rows = 'selectable_rows';
|
||||
|
||||
/**
|
||||
* Controls whether all ticks & labels are shown on a graph's x-axis or just some.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $show_all_ticks = false;
|
||||
|
||||
/**
|
||||
* If true, a row with totals of each DataTable column is added.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $add_total_row = false;
|
||||
|
||||
/**
|
||||
* Controls whether the Series Picker is shown or not. The Series Picker allows users to
|
||||
* choose between displaying data of different columns.
|
||||
*
|
||||
* Default value: true
|
||||
*/
|
||||
public $show_series_picker = true;
|
||||
|
||||
/**
|
||||
* Controls whether the percentage of the total is displayed as a tooltip when hovering over
|
||||
* data points.
|
||||
*
|
||||
* NOTE: Sometimes this percentage is meaningless (when the total of the column values is
|
||||
* not the total number of elements in the set). In this case the tooltip should not be
|
||||
* displayed.
|
||||
*
|
||||
* Default value: true
|
||||
*/
|
||||
public $display_percentage_in_tooltip = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->show_limit_control = false;
|
||||
|
||||
$this->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'show_series_picker',
|
||||
'allow_multi_select_series_picker',
|
||||
'selectable_columns',
|
||||
'selectable_rows',
|
||||
'display_percentage_in_tooltip'
|
||||
));
|
||||
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array(
|
||||
'show_all_ticks',
|
||||
'show_series_picker'
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
|
||||
|
||||
use Piwik\API\Request as ApiRequest;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Period;
|
||||
use Piwik\Plugin\Visualization;
|
||||
|
||||
/**
|
||||
* DataTable visualization that shows DataTable data in an HTML table.
|
||||
*
|
||||
* @property HtmlTable\Config $config
|
||||
*/
|
||||
class HtmlTable extends Visualization
|
||||
{
|
||||
const ID = 'table';
|
||||
const TEMPLATE_FILE = "@CoreVisualizations/_dataTableViz_htmlTable.twig";
|
||||
const FOOTER_ICON = 'icon-table';
|
||||
const FOOTER_ICON_TITLE = 'General_DisplaySimpleTable';
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new HtmlTable\Config();
|
||||
}
|
||||
|
||||
public static function getDefaultRequestConfig()
|
||||
{
|
||||
return new HtmlTable\RequestConfig();
|
||||
}
|
||||
|
||||
public function beforeRender()
|
||||
{
|
||||
if ($this->requestConfig->idSubtable
|
||||
&& $this->config->show_embedded_subtable) {
|
||||
|
||||
$this->config->show_visualization_only = true;
|
||||
}
|
||||
|
||||
if ($this->requestConfig->idSubtable) {
|
||||
$this->config->show_totals_row = false;
|
||||
}
|
||||
|
||||
foreach (Metrics::getMetricIdsToProcessReportTotal() as $metricId) {
|
||||
$this->config->report_ratio_columns[] = Metrics::getReadableColumnName($metricId);
|
||||
}
|
||||
if (!empty($this->report)) {
|
||||
foreach ($this->report->getMetricNamesToProcessReportTotals() as $metricName) {
|
||||
$this->config->report_ratio_columns[] = $metricName;
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to get a datatable\map
|
||||
$period = Common::getRequestVar('period', 'day', 'string');
|
||||
if (Period\Range::parseDateRange($period)) {
|
||||
$period = 'range';
|
||||
}
|
||||
|
||||
if ($this->dataTable->getRowsCount()) {
|
||||
$request = new ApiRequest(array(
|
||||
'method' => 'API.get',
|
||||
'module' => 'API',
|
||||
'action' => 'get',
|
||||
'format' => 'original',
|
||||
'filter_limit' => '-1',
|
||||
'disable_generic_filters' => 1,
|
||||
'expanded' => 0,
|
||||
'flat' => 0,
|
||||
'filter_offset' => 0,
|
||||
'period' => $period,
|
||||
'showColumns' => implode(',', $this->config->columns_to_display),
|
||||
'columns' => implode(',', $this->config->columns_to_display),
|
||||
'pivotBy' => ''
|
||||
));
|
||||
|
||||
$dataTable = $request->process();
|
||||
$this->assignTemplateVar('siteSummary', $dataTable);
|
||||
}
|
||||
|
||||
if ($this->isPivoted()) {
|
||||
$this->config->columns_to_display = $this->dataTable->getColumns();
|
||||
}
|
||||
}
|
||||
|
||||
public function beforeGenericFiltersAreAppliedToLoadedDataTable()
|
||||
{
|
||||
if ($this->isPivoted()) {
|
||||
$this->config->columns_to_display = $this->dataTable->getColumns();
|
||||
|
||||
$this->dataTable->applyQueuedFilters();
|
||||
}
|
||||
|
||||
parent::beforeGenericFiltersAreAppliedToLoadedDataTable();
|
||||
}
|
||||
|
||||
protected function isPivoted()
|
||||
{
|
||||
return $this->requestConfig->pivotBy || Common::getRequestVar('pivotBy', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to compute a custom cell HTML attributes (such as style).
|
||||
*
|
||||
* @param Row $row
|
||||
* @param $column
|
||||
* @return array Array of name => value pairs.
|
||||
*/
|
||||
public function getCellHtmlAttributes(Row $row, $column)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class AllColumns extends HtmlTable
|
||||
{
|
||||
const ID = 'tableAllColumns';
|
||||
const FOOTER_ICON = 'icon-table-more';
|
||||
const FOOTER_ICON_TITLE = 'General_DisplayTableWithMoreMetrics';
|
||||
|
||||
public function beforeRender()
|
||||
{
|
||||
$this->config->show_extra_columns = true;
|
||||
|
||||
parent::beforeRender();
|
||||
}
|
||||
|
||||
public function beforeGenericFiltersAreAppliedToLoadedDataTable()
|
||||
{
|
||||
$this->config->datatable_css_class = 'dataTableVizAllColumns';
|
||||
|
||||
$this->dataTable->filter('AddColumnsProcessedMetrics');
|
||||
|
||||
$properties = $this->config;
|
||||
|
||||
$this->dataTable->filter(function (DataTable $dataTable) use ($properties) {
|
||||
$columnsToDisplay = array('label', 'nb_visits');
|
||||
|
||||
$columns = $dataTable->getColumns();
|
||||
|
||||
if (in_array('nb_uniq_visitors', $columns)) {
|
||||
$columnsToDisplay[] = 'nb_uniq_visitors';
|
||||
}
|
||||
|
||||
if (in_array('nb_users', $columns)) {
|
||||
$columnsToDisplay[] = 'nb_users';
|
||||
}
|
||||
|
||||
$columnsToDisplay = array_merge(
|
||||
$columnsToDisplay, array('nb_actions', 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate')
|
||||
);
|
||||
|
||||
// only display conversion rate for the plugins that do not provide "per goal" metrics
|
||||
// otherwise, conversion rate is meaningless as a whole (since we don't process 'cross goals' conversions)
|
||||
if (!$properties->show_goals) {
|
||||
$columnsToDisplay[] = 'conversion_rate';
|
||||
}
|
||||
|
||||
$properties->columns_to_display = $columnsToDisplay;
|
||||
});
|
||||
}
|
||||
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
unset($this->requestConfig->request_parameters_to_modify['pivotBy']);
|
||||
unset($this->requestConfig->request_parameters_to_modify['pivotByColumn']);
|
||||
}
|
||||
|
||||
protected function isPivoted()
|
||||
{
|
||||
return false; // Pivot not supported
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
|
||||
|
||||
use Piwik\ViewDataTable\Config as VisualizationConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class Config extends VisualizationConfig
|
||||
{
|
||||
|
||||
/**
|
||||
* If this property is set to true, subtables will be shown as embedded in the original table.
|
||||
* If false, subtables will be shown as whole tables between rows.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $show_embedded_subtable = false;
|
||||
|
||||
/**
|
||||
* Controls whether the entire DataTable should be rendered (including subtables) or just one
|
||||
* specific table in the tree.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $show_expanded = false;
|
||||
|
||||
/**
|
||||
* When showing an expanded datatable, this property controls whether rows with subtables are
|
||||
* replaced with their subtables, or if they are shown alongside their subtables.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $replace_row_with_subtable = false;
|
||||
|
||||
/**
|
||||
* Controls whether any DataTable Row Action icons are shown. If true, no icons are shown.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $disable_row_actions = false;
|
||||
|
||||
/**
|
||||
* Controls whether the row evolution DataTable Row Action icon is shown or not.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $disable_row_evolution = false;
|
||||
|
||||
/**
|
||||
* If true, the 'label', 'nb_visits', 'nb_uniq_visitors' (if present), 'nb_actions',
|
||||
* 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate' and 'conversion_rate' (if
|
||||
* goals view is not allowed) are displayed.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $show_extra_columns = false;
|
||||
|
||||
/**
|
||||
* If true, conversions for each existing goal will be displayed for the visits in
|
||||
* each row.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $show_goals_columns = false;
|
||||
|
||||
/**
|
||||
* If true, subtables will not be loaded when rows are clicked, but only if the
|
||||
* 'show_goals_columns' property is also true.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $disable_subtable_when_show_goals = false;
|
||||
|
||||
/**
|
||||
* If true, the summary row will be colored differently than all other DataTable rows.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $highlight_summary_row = false;
|
||||
|
||||
/**
|
||||
* If true, the totals row will be shown
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $show_totals_row = true;
|
||||
|
||||
/**
|
||||
* A list of columns that support showing the ratio percentage on hover
|
||||
* @var array
|
||||
*/
|
||||
public $report_ratio_columns = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->enable_sort = true;
|
||||
$this->datatable_js_type = 'DataTable';
|
||||
|
||||
$this->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'show_extra_columns',
|
||||
'show_goals_columns',
|
||||
'disable_row_evolution',
|
||||
'disable_row_actions',
|
||||
'enable_sort',
|
||||
'keep_summary_row',
|
||||
'subtable_controller_action',
|
||||
));
|
||||
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array(
|
||||
'show_expanded',
|
||||
'disable_row_actions',
|
||||
'disable_row_evolution',
|
||||
'show_extra_columns',
|
||||
'show_goals_columns',
|
||||
'disable_subtable_when_show_goals',
|
||||
'keep_summary_row',
|
||||
'highlight_summary_row',
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Config as PiwikConfig;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\ViewDataTable\RequestConfig as VisualizationRequestConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class RequestConfig extends VisualizationRequestConfig
|
||||
{
|
||||
|
||||
/**
|
||||
* Controls whether the summary row is displayed on every page of the datatable view or not.
|
||||
* If false, the summary row will be treated as the last row of the dataset and will only visible
|
||||
* when viewing the last rows.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $keep_summary_row = false;
|
||||
|
||||
/**
|
||||
* Controls whether the totals row is displayed on every page of the datatable view or not.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $keep_totals_row = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->totals = true;
|
||||
$this->filter_limit = PiwikConfig::getInstance()->General['datatable_default_limit'];
|
||||
|
||||
if (Common::getRequestVar('enable_filter_excludelowpop', false) == '1') {
|
||||
if (Common::getRequestVar('flat', 0, 'int') === 1) {
|
||||
$this->filter_excludelowpop = 'nb_visits';
|
||||
} else {
|
||||
$this->filter_excludelowpop = Metrics::INDEX_NB_VISITS;
|
||||
}
|
||||
|
||||
$this->filter_excludelowpop_value = false;
|
||||
}
|
||||
|
||||
$this->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'search_recursive',
|
||||
'filter_limit',
|
||||
'filter_offset',
|
||||
'filter_sort_column',
|
||||
'filter_sort_order',
|
||||
'keep_summary_row',
|
||||
'keep_totals_row'
|
||||
));
|
||||
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array(
|
||||
'keep_summary_row',
|
||||
'keep_totals_row',
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* DataTable visualization that displays DataTable data in a JQPlot graph.
|
||||
* TODO: should merge all this logic w/ jqplotdatagenerator & 'Chart' visualizations.
|
||||
*
|
||||
* @property JqplotGraph\Config $config
|
||||
*/
|
||||
abstract class JqplotGraph extends Graph
|
||||
{
|
||||
const ID = 'jqplot_graph';
|
||||
const TEMPLATE_FILE = '@CoreVisualizations/_dataTableViz_jqplotGraph.twig';
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new JqplotGraph\Config();
|
||||
}
|
||||
|
||||
public function getGraphData($dataTable, $properties)
|
||||
{
|
||||
$dataGenerator = $this->makeDataGenerator($properties);
|
||||
|
||||
return $dataGenerator->generate($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $properties
|
||||
* @return JqplotDataGenerator
|
||||
*/
|
||||
abstract protected function makeDataGenerator($properties);
|
||||
}
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php';
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php';
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php';
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
|
||||
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
|
||||
/**
|
||||
* Visualization that renders HTML for a Bar graph using jqPlot.
|
||||
*/
|
||||
class Bar extends JqplotGraph
|
||||
{
|
||||
const ID = 'graphVerticalBar';
|
||||
const FOOTER_ICON = 'icon-chart-bar';
|
||||
const FOOTER_ICON_TITLE = 'General_VBarGraph';
|
||||
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
parent::beforeLoadDataTable();
|
||||
|
||||
$this->config->datatable_js_type = 'JqplotBarGraphDataTable';
|
||||
}
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
$config = new Config();
|
||||
$config->max_graph_elements = 6;
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function makeDataGenerator($properties)
|
||||
{
|
||||
return JqplotDataGenerator::factory('bar', $properties);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\Graph\Config as GraphConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class Config extends GraphConfig
|
||||
{
|
||||
/**
|
||||
* The name of the JavaScript class to use as this graph's external series toggle. The class
|
||||
* must be a subclass of JQPlotExternalSeriesToggle.
|
||||
*
|
||||
* @see self::EXTERNAL_SERIES_TOGGLE_SHOW_ALL
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $external_series_toggle = false;
|
||||
|
||||
/**
|
||||
* Whether the graph should show all loaded series upon initial display.
|
||||
*
|
||||
* @see self::EXTERNAL_SERIES_TOGGLE
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $external_series_toggle_show_all = false;
|
||||
|
||||
/**
|
||||
* The number of x-axis ticks for each x-axis label.
|
||||
*
|
||||
* Default: 2
|
||||
*/
|
||||
public $x_axis_step_size = 2;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->show_exclude_low_population = false;
|
||||
$this->show_offset_information = false;
|
||||
$this->show_pagination_control = false;
|
||||
$this->show_exclude_low_population = false;
|
||||
$this->show_search = false;
|
||||
$this->show_export_as_image_icon = true;
|
||||
$this->y_axis_unit = '';
|
||||
|
||||
$this->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'external_series_toggle',
|
||||
'external_series_toggle_show_all'
|
||||
));
|
||||
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array('x_axis_step_size'));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
use Piwik\Site;
|
||||
|
||||
/**
|
||||
* Visualization that renders HTML for a line graph using jqPlot.
|
||||
*
|
||||
* @property Evolution\Config $config
|
||||
*/
|
||||
class Evolution extends JqplotGraph
|
||||
{
|
||||
const ID = 'graphEvolution';
|
||||
const SERIES_COLOR_COUNT = 8;
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new Evolution\Config();
|
||||
}
|
||||
|
||||
public function beforeRender()
|
||||
{
|
||||
parent::beforeRender();
|
||||
|
||||
$this->config->show_flatten_table = false;
|
||||
$this->config->datatable_js_type = 'JqplotEvolutionGraphDataTable';
|
||||
}
|
||||
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
$this->calculateEvolutionDateRange();
|
||||
|
||||
parent::beforeLoadDataTable();
|
||||
|
||||
// period will be overridden when 'range' is requested in the UI
|
||||
// but the graph will display for each day of the range.
|
||||
// Default 'range' behavior is to return the 'sum' for the range
|
||||
if (Common::getRequestVar('period', false) == 'range') {
|
||||
$this->requestConfig->request_parameters_to_modify['period'] = 'day';
|
||||
}
|
||||
|
||||
$this->config->custom_parameters['columns'] = $this->config->columns_to_display;
|
||||
}
|
||||
|
||||
public function afterAllFiltersAreApplied()
|
||||
{
|
||||
parent::afterAllFiltersAreApplied();
|
||||
|
||||
if (false === $this->config->x_axis_step_size) {
|
||||
$rowCount = $this->dataTable->getRowsCount();
|
||||
|
||||
$this->config->x_axis_step_size = $this->getDefaultXAxisStepSize($rowCount);
|
||||
}
|
||||
}
|
||||
|
||||
protected function makeDataGenerator($properties)
|
||||
{
|
||||
return JqplotDataGenerator::factory('evolution', $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the period, date and evolution_{$period}_last_n query parameters,
|
||||
* calculates the date range this evolution chart will display data for.
|
||||
*/
|
||||
private function calculateEvolutionDateRange()
|
||||
{
|
||||
$period = Common::getRequestVar('period');
|
||||
$idSite = Common::getRequestVar('idSite');
|
||||
$timezone = Site::getTimezoneFor($idSite);
|
||||
|
||||
$defaultLastN = self::getDefaultLastN($period);
|
||||
$originalDate = Common::getRequestVar('date', 'last' . $defaultLastN, 'string');
|
||||
|
||||
if ('range' != $period) { // show evolution limit if the period is not a range
|
||||
$this->config->show_limit_control = true;
|
||||
$this->config->show_periods = true;
|
||||
|
||||
// set the evolution_{$period}_last_n query param
|
||||
if (Range::parseDateRange($originalDate)) {
|
||||
// if a multiple period
|
||||
|
||||
// overwrite last_n param using the date range
|
||||
$oPeriod = new Range($period, $originalDate, $timezone);
|
||||
$lastN = count($oPeriod->getSubperiods());
|
||||
|
||||
} else {
|
||||
// if not a multiple period
|
||||
list($newDate, $lastN) = self::getDateRangeAndLastN($period, $originalDate, $defaultLastN);
|
||||
$this->requestConfig->request_parameters_to_modify['date'] = $newDate;
|
||||
$this->config->custom_parameters['dateUsedInGraph'] = $newDate;
|
||||
}
|
||||
|
||||
$lastNParamName = self::getLastNParamName($period);
|
||||
$this->config->custom_parameters[$lastNParamName] = $lastN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entire date range and lastN value for the current request, based on
|
||||
* a period type and end date.
|
||||
*
|
||||
* @param string $period The period type, 'day', 'week', 'month' or 'year'
|
||||
* @param string $endDate The end date.
|
||||
* @param int|null $defaultLastN The default lastN to use. If null, the result of
|
||||
* getDefaultLastN is used.
|
||||
* @return array An array w/ two elements. The first is a whole date range and the second
|
||||
* is the lastN number used, ie, array('2010-01-01,2012-01-02', 2).
|
||||
*/
|
||||
public static function getDateRangeAndLastN($period, $endDate, $defaultLastN = null)
|
||||
{
|
||||
if ($defaultLastN === null) {
|
||||
$defaultLastN = self::getDefaultLastN($period);
|
||||
}
|
||||
|
||||
$lastNParamName = self::getLastNParamName($period);
|
||||
$lastN = Common::getRequestVar($lastNParamName, $defaultLastN, 'int');
|
||||
|
||||
$site = new Site(Common::getRequestVar('idSite'));
|
||||
|
||||
$dateRange = Range::getRelativeToEndDate($period, 'last' . $lastN, $endDate, $site);
|
||||
|
||||
return array($dateRange, $lastN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default last N number of dates to display for a given period.
|
||||
*
|
||||
* @param string $period 'day', 'week', 'month' or 'year'
|
||||
* @return int
|
||||
*/
|
||||
public static function getDefaultLastN($period)
|
||||
{
|
||||
switch ($period) {
|
||||
case 'week':
|
||||
return 26;
|
||||
case 'month':
|
||||
return 24;
|
||||
case 'year':
|
||||
return 5;
|
||||
case 'day':
|
||||
default:
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query parameter that stores the lastN number of periods to get for
|
||||
* the evolution graph.
|
||||
*
|
||||
* @param string $period The period type, 'day', 'week', 'month' or 'year'.
|
||||
* @return string
|
||||
*/
|
||||
public static function getLastNParamName($period)
|
||||
{
|
||||
return "evolution_{$period}_last_n";
|
||||
}
|
||||
|
||||
public function getDefaultXAxisStepSize($countGraphElements)
|
||||
{
|
||||
// when the number of elements plotted can be small, make sure the X legend is useful
|
||||
if ($countGraphElements <= 7) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$periodLabel = Common::getRequestVar('period');
|
||||
|
||||
switch ($periodLabel) {
|
||||
case 'day':
|
||||
case 'range':
|
||||
$steps = 5;
|
||||
break;
|
||||
case 'week':
|
||||
$steps = 4;
|
||||
break;
|
||||
case 'month':
|
||||
$steps = 5;
|
||||
break;
|
||||
case 'year':
|
||||
$steps = 5;
|
||||
break;
|
||||
default:
|
||||
$steps = 5;
|
||||
break;
|
||||
}
|
||||
|
||||
$paddedCount = $countGraphElements + 2; // pad count so last label won't be cut off
|
||||
|
||||
return ceil($paddedCount / $steps);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
|
||||
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Config as JqplotGraphConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class Config extends JqplotGraphConfig
|
||||
{
|
||||
/**
|
||||
* Whether to show a line graph or a bar graph.
|
||||
*
|
||||
* Default value: true
|
||||
*/
|
||||
public $show_line_graph = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->show_all_views_icons = false;
|
||||
$this->show_table = false;
|
||||
$this->show_table_all_columns = false;
|
||||
$this->hide_annotations_view = false;
|
||||
$this->x_axis_step_size = false;
|
||||
$this->show_line_graph = true;
|
||||
|
||||
$this->addPropertiesThatShouldBeAvailableClientSide(array('show_line_graph'));
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array('show_line_graph'));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
|
||||
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
|
||||
|
||||
/**
|
||||
* Visualization that renders HTML for a Pie graph using jqPlot.
|
||||
*/
|
||||
class Pie extends JqplotGraph
|
||||
{
|
||||
const ID = 'graphPie';
|
||||
const FOOTER_ICON = 'icon-chart-pie';
|
||||
const FOOTER_ICON_TITLE = 'General_Piechart';
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
$config = new Config();
|
||||
$config->max_graph_elements = 6;
|
||||
$config->allow_multi_select_series_picker = false;
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function beforeRender()
|
||||
{
|
||||
parent::beforeRender();
|
||||
|
||||
$this->config->show_all_ticks = true;
|
||||
$this->config->datatable_js_type = 'JqplotPieGraphDataTable';
|
||||
}
|
||||
|
||||
public function afterAllFiltersAreApplied()
|
||||
{
|
||||
parent::afterAllFiltersAreApplied();
|
||||
|
||||
$metricColumn = reset($this->config->columns_to_display);
|
||||
|
||||
if ($metricColumn == 'label') {
|
||||
$metricColumn = next($this->config->columns_to_display);
|
||||
}
|
||||
|
||||
$this->config->columns_to_display = array($metricColumn ? : 'nb_visits');
|
||||
}
|
||||
|
||||
protected function makeDataGenerator($properties)
|
||||
{
|
||||
return JqplotDataGenerator::factory('pie', $properties);
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Plugin\ViewDataTable;
|
||||
|
||||
/**
|
||||
* Reads the requested DataTable from the API and prepare data for the Sparkline view.
|
||||
*
|
||||
*/
|
||||
class Sparkline extends ViewDataTable
|
||||
{
|
||||
const ID = 'sparkline';
|
||||
|
||||
/**
|
||||
* @see ViewDataTable::main()
|
||||
* @return mixed
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
// If period=range, we force the sparkline to draw daily data points
|
||||
$period = Common::getRequestVar('period');
|
||||
if ($period == 'range') {
|
||||
$_GET['period'] = 'day';
|
||||
}
|
||||
|
||||
$this->loadDataTableFromAPI();
|
||||
|
||||
// then revert the hack for potentially subsequent getRequestVar
|
||||
$_GET['period'] = $period;
|
||||
|
||||
$values = $this->getValuesFromDataTable($this->dataTable);
|
||||
if (empty($values)) {
|
||||
$values = array_fill(0, 30, 0);
|
||||
}
|
||||
|
||||
$graph = new \Piwik\Visualization\Sparkline();
|
||||
$graph->setValues($values);
|
||||
|
||||
$height = Common::getRequestVar('height', 0, 'int');
|
||||
if (!empty($height)) {
|
||||
$graph->setHeight($height);
|
||||
}
|
||||
|
||||
$width = Common::getRequestVar('width', 0, 'int');
|
||||
if (!empty($width)) {
|
||||
$graph->setWidth($width);
|
||||
}
|
||||
|
||||
$graph->main();
|
||||
|
||||
return $graph->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable\Map $dataTableMap
|
||||
* @param string $columnToPlot
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getValuesFromDataTableMap($dataTableMap, $columnToPlot)
|
||||
{
|
||||
$dataTableMap->applyQueuedFilters();
|
||||
|
||||
$values = array();
|
||||
|
||||
foreach ($dataTableMap->getDataTables() as $table) {
|
||||
|
||||
if ($table->getRowsCount() > 1) {
|
||||
throw new Exception("Expecting only one row per DataTable");
|
||||
}
|
||||
|
||||
$value = 0;
|
||||
$onlyRow = $table->getFirstRow();
|
||||
|
||||
if (false !== $onlyRow) {
|
||||
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->config->columns_to_display;
|
||||
|
||||
$columnToPlot = false;
|
||||
|
||||
if (!empty($columns)) {
|
||||
$columnToPlot = reset($columns);
|
||||
if ($columnToPlot == 'label') {
|
||||
$columnToPlot = next($columns);
|
||||
}
|
||||
}
|
||||
|
||||
// a Set 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 DataTable\Map) {
|
||||
$values = $this->getValuesFromDataTableMap($dataTable, $columnToPlot);
|
||||
} elseif ($this->dataTable instanceof DataTable) {
|
||||
$values = $this->dataTable->getColumn($columnToPlot);
|
||||
} else {
|
||||
$values = false;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Plugin\ViewDataTable;
|
||||
use Piwik\Url;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* Reads the requested DataTable from the API and prepares data for the Sparklines view. It can display any amount
|
||||
* of sparklines. Within a reporting page sparklines are shown in 2 columns, in a dashboard or when exported as a widget
|
||||
* the sparklines are shown in one column.
|
||||
*
|
||||
* The sparklines view currently only supports requesting columns from the same API (the API method of the defining
|
||||
* report) via {Sparklines\Config::addSparklineMetric($columns = array('nb_visits', 'nb_unique_visitors'))}.
|
||||
*
|
||||
* Example:
|
||||
* $view->config->addSparklineMetric('nb_visits'); // if an array of metrics given, they will be displayed comma separated
|
||||
* $view->config->addTranslation('nb_visits', 'Visits');
|
||||
* Results in: [sparkline image] X visits
|
||||
* Data is fetched from the configured $view->requestConfig->apiMethodToRequestDataTable.
|
||||
*
|
||||
* In case you want to add any custom sparklines from any other API method you can call
|
||||
* {@link Sparklines\Config::addSparkline()}.
|
||||
*
|
||||
* Example:
|
||||
* $sparklineUrlParams = array('columns' => array('nb_visits));
|
||||
* $evolution = array('currentValue' => 5, 'pastValue' => 10, 'tooltip' => 'Foo bar');
|
||||
* $view->config->addSparkline($sparklineUrlParams, $value = 5, $description = 'Visits', $evolution);
|
||||
*
|
||||
* @property Sparklines\Config $config
|
||||
*/
|
||||
class Sparklines extends ViewDataTable
|
||||
{
|
||||
const ID = 'sparklines';
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new Sparklines\Config();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ViewDataTable::main()
|
||||
* @return mixed
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$view = new View('@CoreVisualizations/_dataTableViz_sparklines.twig');
|
||||
|
||||
$columnsList = array();
|
||||
if ($this->config->hasSparklineMetrics()) {
|
||||
foreach ($this->config->getSparklineMetrics() as $cols) {
|
||||
$columns = $cols['columns'];
|
||||
if (!is_array($columns)) {
|
||||
$columns = array($columns);
|
||||
}
|
||||
|
||||
$columnsList = array_merge($columns, $columnsList);
|
||||
}
|
||||
}
|
||||
|
||||
$view->allMetricsDocumentation = Metrics::getDefaultMetricsDocumentation();
|
||||
|
||||
$this->requestConfig->request_parameters_to_modify['columns'] = $columnsList;
|
||||
$this->requestConfig->request_parameters_to_modify['format_metrics'] = '1';
|
||||
|
||||
if (!empty($this->requestConfig->apiMethodToRequestDataTable)) {
|
||||
$this->fetchConfiguredSparklines();
|
||||
}
|
||||
|
||||
$view->sparklines = $this->config->getSortedSparklines();
|
||||
$view->isWidget = Common::getRequestVar('widget', 0, 'int');
|
||||
$view->titleAttributes = $this->config->title_attributes;
|
||||
$view->footerMessage = $this->config->show_footer_message;
|
||||
$view->areSparklinesLinkable = $this->config->areSparklinesLinkable();
|
||||
|
||||
$view->title = '';
|
||||
if ($this->config->show_title) {
|
||||
$view->title = $this->config->title;
|
||||
}
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
private function fetchConfiguredSparklines()
|
||||
{
|
||||
$data = $this->loadDataTableFromAPI();
|
||||
|
||||
$this->applyFilters($data);
|
||||
|
||||
if (!$this->config->hasSparklineMetrics()) {
|
||||
foreach ($data->getColumns() as $column) {
|
||||
$this->config->addSparklineMetric($column);
|
||||
}
|
||||
}
|
||||
|
||||
$translations = $this->config->translations;
|
||||
|
||||
$firstRow = $data->getFirstRow();
|
||||
|
||||
foreach ($this->config->getSparklineMetrics() as $sparklineMetric) {
|
||||
$column = $sparklineMetric['columns'];
|
||||
$order = $sparklineMetric['order'];
|
||||
|
||||
if ($column === 'label') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($column)) {
|
||||
$this->config->addPlaceholder($order);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_array($column)) {
|
||||
$column = array($column);
|
||||
}
|
||||
|
||||
$values = array();
|
||||
$descriptions = array();
|
||||
|
||||
foreach ($column as $col) {
|
||||
$value = $firstRow->getColumn($col);
|
||||
|
||||
if ($value === false) {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
$values[] = $value;
|
||||
$descriptions[] = isset($translations[$col]) ? $translations[$col] : $col;
|
||||
}
|
||||
|
||||
$sparklineUrlParams = array(
|
||||
'columns' => $column,
|
||||
'module' => $this->requestConfig->getApiModuleToRequest(),
|
||||
'action' => $this->requestConfig->getApiMethodToRequest()
|
||||
);
|
||||
|
||||
$this->config->addSparkline($sparklineUrlParams, $values, $descriptions, null, $order);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyFilters(DataTable\DataTableInterface $table)
|
||||
{
|
||||
foreach ($this->config->getPriorityFilters() as $filter) {
|
||||
$table->filter($filter[0], $filter[1]);
|
||||
}
|
||||
|
||||
// queue other filters so they can be applied later if queued filters are disabled
|
||||
foreach ($this->config->getPresentationFilters() as $filter) {
|
||||
$table->queueFilter($filter[0], $filter[1]);
|
||||
}
|
||||
|
||||
$table->applyQueuedFilters();
|
||||
}
|
||||
}
|
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Visualizations\Sparklines;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Filter\CalculateEvolutionFilter;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\NoAccessException;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Site;
|
||||
use Piwik\Url;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from Sparklines.
|
||||
*/
|
||||
class Config extends \Piwik\ViewDataTable\Config
|
||||
{
|
||||
/**
|
||||
* Holds metrics / column names that will be used to fetch data from the configured $requestConfig API.
|
||||
* Default value: array
|
||||
*/
|
||||
private $sparkline_metrics = array();
|
||||
|
||||
/**
|
||||
* Holds the actual sparkline entries based on fetched data that will be used in the template.
|
||||
* @var array
|
||||
*/
|
||||
private $sparklines = array();
|
||||
|
||||
/**
|
||||
* If false, will not link them with any evolution graph
|
||||
* @var bool
|
||||
*/
|
||||
private $evolutionGraphLinkable = true;
|
||||
|
||||
/**
|
||||
* Adds possibility to set html attributes on the sparklines title / headline. For example can be used
|
||||
* to set an angular directive
|
||||
* @var string
|
||||
*/
|
||||
public $title_attributes = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->translations = Metrics::getDefaultMetricTranslations();
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @return array
|
||||
*/
|
||||
public function getSparklineMetrics()
|
||||
{
|
||||
return $this->sparkline_metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSparklineMetrics()
|
||||
{
|
||||
return !empty($this->sparkline_metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing sparkline entry. Especially useful in dataTable filters in case sparklines should be not
|
||||
* displayed depending on the fetched data.
|
||||
*
|
||||
* Example:
|
||||
* $config->addSparklineMetric('nb_users');
|
||||
* $config->filters[] = function ($dataTable) use ($config) {
|
||||
* if ($dataTable->getFirstRow()->getColumn('nb_users') == 0) {
|
||||
* // do not show a sparkline if there are no recorded users
|
||||
* $config->removeSparklineMetric('nb_users');
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param array|string $metricNames The name of the metrics in the same format they were used when added via
|
||||
* {@link addSparklineMetric}
|
||||
*/
|
||||
public function removeSparklineMetric($metricNames)
|
||||
{
|
||||
foreach ($this->sparkline_metrics as $index => $metric) {
|
||||
if ($metric['columns'] === $metricNames) {
|
||||
array_splice($this->sparkline_metrics, $index, 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an existing sparkline entry with different columns. Especially useful in dataTable filters in case
|
||||
* sparklines should be not displayed depending on the fetched data.
|
||||
*
|
||||
* Example:
|
||||
* $config->addSparklineMetric('nb_users');
|
||||
* $config->filters[] = function ($dataTable) use ($config) {
|
||||
* if ($dataTable->getFirstRow()->getColumn('nb_users') == 0) {
|
||||
* // instead of showing the sparklines for users, show a placeholder if there are no recorded users
|
||||
* $config->replaceSparklineMetric(array('nb_users'), '');
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param array|string $metricNames The name of the metrics in the same format they were used when added via
|
||||
* {@link addSparklineMetric}
|
||||
* @param array|string $replacementColumns The removed columns will be replaced with these columns
|
||||
*/
|
||||
public function replaceSparklineMetric($metricNames, $replacementColumns)
|
||||
{
|
||||
foreach ($this->sparkline_metrics as $index => $metric) {
|
||||
if ($metric['columns'] === $metricNames) {
|
||||
$this->sparkline_metrics[$index]['columns'] = $replacementColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new sparkline.
|
||||
*
|
||||
* It will show a sparkline image, the value of the resolved metric name and a descrption. Optionally, multiple
|
||||
* values can be shown after a sparkline image by passing multiple metric names
|
||||
* (eg array('nb_visits', 'nb_actions')). The data will be requested from the configured api method see
|
||||
* {@link Piwik\ViewDataTable\RequestConfig::$apiMethodToRequestDataTable}.
|
||||
*
|
||||
* Example:
|
||||
* $config->addSparklineMetric('nb_visits');
|
||||
* $config->addTranslation('nb_visits', 'Visits');
|
||||
* Results in: [sparkline image] X visits
|
||||
*
|
||||
* Example:
|
||||
* $config->addSparklineMetric(array('nb_visits', 'nb_actions'));
|
||||
* $config->addTranslations(array('nb_visits' => 'Visits', 'nb_actions' => 'Actions'));
|
||||
* Results in: [sparkline image] X visits, Y actions
|
||||
*
|
||||
* @param string|array $metricName Either one metric name (eg 'nb_visits') or an array of metric names
|
||||
* @param int|null $order Defines the order. The lower the order the earlier the sparkline will be displayed.
|
||||
* By default the sparkline will be appended to the end.
|
||||
*/
|
||||
public function addSparklineMetric($metricName, $order = null)
|
||||
{
|
||||
$this->sparkline_metrics[] = array(
|
||||
'columns' => $metricName,
|
||||
'order' => $order
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a placeholder. In this case nothing will be shown, neither a sparkline nor any description. This can be
|
||||
* useful if you want to have some kind of separator. Eg if you want to have a sparkline on the left side but
|
||||
* not sparkline on the right side.
|
||||
*
|
||||
* @param int|null $order Defines the order. The lower the order the earlier the sparkline will be displayed.
|
||||
* By default the sparkline will be appended to the end.
|
||||
*/
|
||||
public function addPlaceholder($order = null)
|
||||
{
|
||||
$this->sparklines[] = array(
|
||||
'url' => '',
|
||||
'metrics' => array(),
|
||||
'order' => $this->getSparklineOrder($order)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sparkline to be displayed to the view.
|
||||
*
|
||||
* Each sparkline can consist of one or multiple metrics. One metric consists of a value and a description. By
|
||||
* default the value is shown first, then the description. The description can optionally contain a '%s' in case
|
||||
* the value shall be displayed within the description. If multiple metrics are given, they will be separated by
|
||||
* a comma.
|
||||
*
|
||||
* @param array $requestParamsForSparkline You need to at least set a module / action eg
|
||||
* array('columns' => array('nb_visit'), 'module' => '', 'action' => '')
|
||||
* @param int|float|string|array $value Either the metric value or an array of values.
|
||||
* @param string|array $description Either one description or an array of descriptions. If an array, both
|
||||
* $value and $description need the same amount of array entries.
|
||||
* $description[0] should be the description for $value[0].
|
||||
* $description should be already translated. If $value should appear
|
||||
* somewhere within the text a `%s` can be used in the translation.
|
||||
* @param array|null $evolution Optional array containing at least the array keys 'currentValue' and
|
||||
* 'pastValue' which are needed to calculate the correct percentage.
|
||||
* An optional 'tooltip' can be set as well. Eg
|
||||
* array('currentValue' => 10, 'pastValue' => 20,
|
||||
* 'tooltip' => '10 visits in 2015-07-26 compared to 20 visits in 2015-07-25')
|
||||
* @param int $order Defines the order. The lower the order the earlier the sparkline will be
|
||||
* displayed. By default the sparkline will be appended to the end.
|
||||
* @throws \Exception In case an evolution parameter is set but has wrong data structure
|
||||
*/
|
||||
public function addSparkline($requestParamsForSparkline, $value, $description, $evolution = null, $order = null)
|
||||
{
|
||||
$metrics = array();
|
||||
|
||||
if (is_array($value)) {
|
||||
$values = $value;
|
||||
} else {
|
||||
$values = array($value);
|
||||
}
|
||||
|
||||
if (!is_array($description)) {
|
||||
$description = array($description);
|
||||
}
|
||||
|
||||
if (!empty($requestParamsForSparkline['columns'])
|
||||
&& is_array($requestParamsForSparkline['columns'])
|
||||
&& count($requestParamsForSparkline['columns']) === count($values)) {
|
||||
$columns = array_values($requestParamsForSparkline['columns']);
|
||||
} elseif (!empty($requestParamsForSparkline['columns'])
|
||||
&& is_string($requestParamsForSparkline['columns'])
|
||||
&& count($values) === 1) {
|
||||
$columns = array($requestParamsForSparkline['columns']);
|
||||
} else{
|
||||
$columns = array();
|
||||
}
|
||||
|
||||
if (count($values) === count($description)) {
|
||||
foreach ($values as $index => $value) {
|
||||
$metrics[] = array(
|
||||
'column' => isset($columns[$index]) ? $columns[$index] : '',
|
||||
'value' => $value,
|
||||
'description' => $description[$index]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$msg = 'The number of values and descriptions need to be the same to add a sparkline. ';
|
||||
$msg .= 'Values: ' . implode(', ', $values). ' Descriptions: ' . implode(', ', $description);
|
||||
throw new \Exception($msg);
|
||||
}
|
||||
|
||||
if (empty($metrics)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sparkline = array(
|
||||
'url' => $this->getUrlSparkline($requestParamsForSparkline),
|
||||
'metrics' => $metrics,
|
||||
'order' => $this->getSparklineOrder($order)
|
||||
);
|
||||
|
||||
if (!empty($evolution)) {
|
||||
if (!is_array($evolution) ||
|
||||
!array_key_exists('currentValue', $evolution) ||
|
||||
!array_key_exists('pastValue', $evolution)) {
|
||||
throw new \Exception('In order to show an evolution in the sparklines view a currentValue and pastValue array key needs to be present');
|
||||
}
|
||||
|
||||
$evolutionPercent = CalculateEvolutionFilter::calculate($evolution['currentValue'], $evolution['pastValue'], $precision = 1);
|
||||
|
||||
// do not display evolution if evolution percent is 0 and current value is 0
|
||||
if ($evolutionPercent != 0 || $evolution['currentValue'] != 0) {
|
||||
$sparkline['evolution'] = array(
|
||||
'percent' => $evolutionPercent,
|
||||
'tooltip' => !empty($evolution['tooltip']) ? $evolution['tooltip'] : null
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->sparklines[] = $sparkline;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are sparklines and evolution graphs on one page, we try to connect them so that when you click on a
|
||||
* sparkline, the evolution graph will update and show the evolution for that sparkline metric. In some cases
|
||||
* we might falsely connect sparklines with an evolution graph that don't belong together. In this case you can
|
||||
* mark all sparklines as "not linkable". This will prevent the sparklines being linked with an evolution graph.
|
||||
*/
|
||||
public function setNotLinkableWithAnyEvolutionGraph()
|
||||
{
|
||||
$this->evolutionGraphLinkable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether sparklines are linkable with an evolution graph. {@link setNotLinkableWithAnyEvolutionGraph()}
|
||||
*/
|
||||
public function areSparklinesLinkable()
|
||||
{
|
||||
return $this->evolutionGraphLinkable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function getSortedSparklines()
|
||||
{
|
||||
usort($this->sparklines, function ($a, $b) {
|
||||
if ($a['order'] == $b['order']) {
|
||||
return 0;
|
||||
}
|
||||
return ($a['order'] < $b['order']) ? -1 : 1;
|
||||
});
|
||||
|
||||
return $this->sparklines;
|
||||
}
|
||||
|
||||
private function getSparklineOrder($order)
|
||||
{
|
||||
if (!isset($order)) {
|
||||
// make sure to append to the end if nothing set (in the order they are added)
|
||||
$order = 999 + count($this->sparklines);
|
||||
}
|
||||
|
||||
return (int) $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL to a sparkline image for a report served by the current plugin.
|
||||
*
|
||||
* The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function.
|
||||
*
|
||||
* The current site ID and period will be used.
|
||||
*
|
||||
* @param array $customParameters The array of query parameter name/value pairs that
|
||||
* should be set in result URL.
|
||||
* @return string The generated URL.
|
||||
*/
|
||||
private function getUrlSparkline($customParameters = array())
|
||||
{
|
||||
$customParameters['viewDataTable'] = 'sparkline';
|
||||
|
||||
$params = $this->getGraphParamsModified($customParameters);
|
||||
|
||||
// convert array values to comma separated
|
||||
foreach ($params as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = rawurlencode(implode(',', $value));
|
||||
}
|
||||
}
|
||||
$url = Url::getCurrentQueryStringWithParametersModified($params);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of new processed parameters once the parameters are applied.
|
||||
* For example: if you set range=last30 and date=2008-03-10,
|
||||
* the date element of the returned array will be "2008-02-10,2008-03-10"
|
||||
*
|
||||
* Parameters you can set:
|
||||
* - range: last30, previous10, etc.
|
||||
* - date: YYYY-MM-DD, today, yesterday
|
||||
* - period: day, week, month, year
|
||||
*
|
||||
* @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
|
||||
* @throws \Piwik\NoAccessException
|
||||
* @return array
|
||||
*/
|
||||
private function getGraphParamsModified($paramsToSet = array())
|
||||
{
|
||||
if (!isset($paramsToSet['period'])) {
|
||||
$period = Common::getRequestVar('period');
|
||||
} else {
|
||||
$period = $paramsToSet['period'];
|
||||
}
|
||||
|
||||
if ($period == 'range') {
|
||||
return $paramsToSet;
|
||||
}
|
||||
|
||||
if (!isset($paramsToSet['range'])) {
|
||||
$range = 'last30';
|
||||
} else {
|
||||
$range = $paramsToSet['range'];
|
||||
}
|
||||
|
||||
if (!isset($paramsToSet['idSite'])) {
|
||||
$idSite = Common::getRequestVar('idSite');
|
||||
} else {
|
||||
$idSite = $paramsToSet['idSite'];
|
||||
}
|
||||
|
||||
if (!isset($paramsToSet['date'])) {
|
||||
$endDate = Common::getRequestVar('date', 'yesterday', 'string');
|
||||
} else {
|
||||
$endDate = $paramsToSet['date'];
|
||||
}
|
||||
|
||||
$site = new Site($idSite);
|
||||
|
||||
if (is_null($site)) {
|
||||
throw new NoAccessException("Website not initialized, check that you are logged in and/or using the correct token_auth.");
|
||||
}
|
||||
|
||||
$paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $site);
|
||||
|
||||
$params = array_merge($paramsToSet, array('date' => $paramDate));
|
||||
return $params;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\CoreVisualizations\Widgets;
|
||||
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\View;
|
||||
use Piwik\Widget\WidgetConfig;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\Plugins\Goals\API as GoalsAPI;
|
||||
|
||||
class SingleMetricView extends \Piwik\Widget\Widget
|
||||
{
|
||||
public static function configure(WidgetConfig $config)
|
||||
{
|
||||
parent::configure($config);
|
||||
|
||||
$column = Common::getRequestVar('column', '', 'string');
|
||||
|
||||
$config->addParameters(['column' => $column]);
|
||||
$config->setCategoryId('General_Generic');
|
||||
$config->setName('General_Metric');
|
||||
$config->setIsWidgetizable();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$column = Common::getRequestVar('column', 'nb_visits', 'string');
|
||||
|
||||
$goalMetrics = [];
|
||||
$goals = [];
|
||||
|
||||
$idSite = Common::getRequestVar('idSite');
|
||||
$idGoal = Common::getRequestVar('idGoal', false);
|
||||
|
||||
$reportMetadata = Request::processRequest('API.getMetadata', [
|
||||
'idSites' => $idSite,
|
||||
'apiModule' => 'API',
|
||||
'apiAction' => 'get',
|
||||
]);
|
||||
$reportMetadata = reset($reportMetadata);
|
||||
|
||||
$metricTranslations = array_merge($reportMetadata['metrics'], $reportMetadata['processedMetrics']);
|
||||
$metricDocumentations = $reportMetadata['metricsDocumentation'];
|
||||
|
||||
if (PluginManager::getInstance()->isPluginActivated('Goals')) {
|
||||
$reportMetadata = Request::processRequest('API.getMetadata', [
|
||||
'idSites' => $idSite,
|
||||
'apiModule' => 'Goals',
|
||||
'apiAction' => 'get',
|
||||
]);
|
||||
$reportMetadata = reset($reportMetadata);
|
||||
|
||||
$goalMetrics = array_merge(
|
||||
array_keys($reportMetadata['metrics']),
|
||||
array_keys($reportMetadata['processedMetrics'])
|
||||
);
|
||||
$metricDocumentations = array_merge($metricDocumentations, $reportMetadata['metricsDocumentation']);
|
||||
|
||||
$goals = Request::processRequest('Goals.getGoals', ['idSite' => $idSite, 'filter_limit' => '-1'], $default = []);
|
||||
}
|
||||
|
||||
$view = new View("@CoreHome/_angularComponent.twig");
|
||||
$view->componentName = 'piwik-single-metric-view';
|
||||
$view->componentParameters = [
|
||||
'metric' => json_encode($column),
|
||||
'id-goal' => $idGoal === false ? 'undefined' : $idGoal,
|
||||
'goal-metrics' => json_encode($goalMetrics),
|
||||
'goals' => json_encode($goals),
|
||||
'metric-translations' => json_encode($metricTranslations),
|
||||
'metric-documentations' => json_encode($metricDocumentations),
|
||||
];
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<div
|
||||
class="jqplot-seriespicker"
|
||||
ng-class="{open: $ctrl.isPopupVisible}"
|
||||
ng-mouseenter="$ctrl.isPopupVisible = true"
|
||||
ng-mouseleave="$ctrl.onLeavePopup()"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
ng-click="$event.preventDefault(); $event.stopPropagation();"
|
||||
>
|
||||
+
|
||||
</a>
|
||||
<div
|
||||
class="jqplot-seriespicker-popover"
|
||||
ng-if="$ctrl.isPopupVisible"
|
||||
>
|
||||
<p class="headline">{{ ($ctrl.multiselect ? 'General_MetricsToPlot' : 'General_MetricToPlot') | translate }}</p>
|
||||
<p
|
||||
ng-repeat="columnConfig in $ctrl.selectableColumns"
|
||||
class="pickColumn"
|
||||
ng-click="$ctrl.optionSelected(columnConfig.column, $ctrl.columnStates)"
|
||||
>
|
||||
<input
|
||||
class="select"
|
||||
ng-checked="$ctrl.columnStates[columnConfig.column]"
|
||||
ng-attr-type="{{ $ctrl.multiselect ? 'checkbox' : 'radio' }}"
|
||||
/>
|
||||
<label>{{ columnConfig.translation }}</label>
|
||||
</p>
|
||||
<p
|
||||
ng-if="$ctrl.selectableRows.length"
|
||||
class="headline recordsToPlot"
|
||||
>
|
||||
{{ 'General_RecordsToPlot' | translate }}
|
||||
</p>
|
||||
<p
|
||||
ng-repeat="rowConfig in $ctrl.selectableRows"
|
||||
class="pickRow"
|
||||
ng-click="$ctrl.optionSelected(rowConfig.matcher, $ctrl.rowStates)"
|
||||
>
|
||||
<input
|
||||
class="select"
|
||||
ng-checked="$ctrl.rowStates[rowConfig.matcher]"
|
||||
ng-attr-type="{{ $ctrl.multiselect ? 'checkbox' : 'radio' }}"
|
||||
/>
|
||||
<label>{{ rowConfig.label }}</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,144 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* This series picker component is a popup that displays a list of metrics/row
|
||||
* values that can be selected. It's used by certain datatable visualizations
|
||||
* to allow users to select different data series for display.
|
||||
*
|
||||
* Inputs:
|
||||
* - multiselect: true if the picker should allow selecting multiple items, false
|
||||
* if otherwise.
|
||||
* - selectableColumns: the list of selectable metric values. must be a list of
|
||||
* objects with the following properties:
|
||||
* * column: the ID of the column, eg, nb_visits
|
||||
* * translation: the translated text for the column, eg, Visits
|
||||
* - selectableRows: the list of selectable row values. must be a list of objects
|
||||
* with the following properties:
|
||||
* * matcher: the ID of the row
|
||||
* * label: the display text for the row
|
||||
* - selectedColumns: the list of selected columns. should be a list of strings
|
||||
* that correspond to the 'column' property in selectableColumns.
|
||||
* - selectedRows: the list of selected rows. should be a list of strings that
|
||||
* correspond to the 'matcher' property in selectableRows.
|
||||
* - onSelect: expression invoked when a user makes a new selection. invoked
|
||||
* with the following local variables:
|
||||
* * columns: list of IDs of new selected columns, if any
|
||||
* * rows: list of matchers of new selected rows, if any
|
||||
*
|
||||
* Usage:
|
||||
* <piwik-series-picker />
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').component('piwikSeriesPicker', {
|
||||
templateUrl: 'plugins/CoreVisualizations/angularjs/series-picker/series-picker.component.html?cb=' + piwik.cacheBuster,
|
||||
bindings: {
|
||||
multiselect: '<',
|
||||
selectableColumns: '<',
|
||||
selectableRows: '<',
|
||||
selectedColumns: '<',
|
||||
selectedRows: '<',
|
||||
onSelect: '&'
|
||||
},
|
||||
controller: SeriesPickerController
|
||||
});
|
||||
|
||||
SeriesPickerController.$inject = [];
|
||||
|
||||
function SeriesPickerController() {
|
||||
var vm = this;
|
||||
vm.isPopupVisible = false;
|
||||
|
||||
// note: column & row states are separated since it's technically possible (though
|
||||
// highly improbable) that a row value matcher will be the same as a recognized column.
|
||||
vm.columnStates = {};
|
||||
vm.rowStates = {};
|
||||
vm.optionSelected = optionSelected;
|
||||
vm.onLeavePopup = onLeavePopup;
|
||||
vm.$onInit = $onInit;
|
||||
|
||||
function $onInit() {
|
||||
vm.columnStates = getInitialOptionStates(vm.selectableColumns, vm.selectedColumns);
|
||||
vm.rowStates = getInitialOptionStates(vm.selectableRows, vm.selectedRows);
|
||||
}
|
||||
|
||||
function getInitialOptionStates(allOptions, selectedOptions) {
|
||||
var states = {};
|
||||
|
||||
allOptions.forEach(function (columnConfig) {
|
||||
states[columnConfig.column || columnConfig.matcher] = false;
|
||||
});
|
||||
|
||||
selectedOptions.forEach(function (column) {
|
||||
states[column] = true;
|
||||
});
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
function optionSelected(optionValue, optionStates) {
|
||||
if (!vm.multiselect) {
|
||||
unselectOptions(vm.columnStates);
|
||||
unselectOptions(vm.rowStates);
|
||||
}
|
||||
|
||||
optionStates[optionValue] = !optionStates[optionValue];
|
||||
|
||||
if (optionStates[optionValue]) {
|
||||
triggerOnSelectAndClose();
|
||||
}
|
||||
}
|
||||
|
||||
function onLeavePopup() {
|
||||
vm.isPopupVisible = false;
|
||||
|
||||
if (optionsChanged()) {
|
||||
triggerOnSelectAndClose();
|
||||
}
|
||||
}
|
||||
|
||||
function triggerOnSelectAndClose() {
|
||||
if (!vm.onSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.isPopupVisible = false;
|
||||
|
||||
vm.onSelect({
|
||||
columns: getSelected(vm.columnStates),
|
||||
rows: getSelected(vm.rowStates)
|
||||
});
|
||||
}
|
||||
|
||||
function optionsChanged() {
|
||||
return !arrayEqual(getSelected(vm.columnStates), vm.selectedColumns)
|
||||
|| !arrayEqual(getSelected(vm.rowStates), vm.selectedRows);
|
||||
}
|
||||
|
||||
function arrayEqual(lhs, rhs) {
|
||||
if (lhs.length !== rhs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lhs
|
||||
.filter(function (element) { return rhs.indexOf(element) === -1; })
|
||||
.length === 0;
|
||||
}
|
||||
|
||||
function unselectOptions(optionStates) {
|
||||
Object.keys(optionStates).forEach(function (optionName) {
|
||||
optionStates[optionName] = false;
|
||||
});
|
||||
}
|
||||
|
||||
function getSelected(optionStates) {
|
||||
return Object.keys(optionStates).filter(function (optionName) {
|
||||
return !! optionStates[optionName];
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,28 @@
|
||||
piwik-series-picker {
|
||||
display: inline-block;
|
||||
|
||||
.jqplot-seriespicker {
|
||||
&:not(.open) {
|
||||
opacity: .55;
|
||||
}
|
||||
|
||||
&.open { // while open, make sure we're above other series picker icons
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover {
|
||||
position: absolute;
|
||||
|
||||
top: -3px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<div class="singleMetricView" ng-class="{'loading': $ctrl.isLoading}">
|
||||
<piwik-sparkline
|
||||
class="metric-sparkline"
|
||||
params="$ctrl.sparklineParams"
|
||||
>
|
||||
</piwik-sparkline>
|
||||
<div class="metric-value">
|
||||
<span title="{{ $ctrl.metricDocumentation }}">
|
||||
<strong>{{ $ctrl.metricValue }}</strong> {{ ($ctrl.metricTranslation || '').toLowerCase() }}
|
||||
</span>
|
||||
<span class="metricEvolution"
|
||||
ng-if="$ctrl.pastValue !== null"
|
||||
title="{{ 'General_EvolutionSummaryGeneric'|translate:$ctrl.metricValue:$ctrl.getCurrentPeriod():$ctrl.pastValue:$ctrl.pastPeriod:$ctrl.metricChangePercent }}"
|
||||
>
|
||||
<span ng-class="{'positive-evolution': $ctrl.metricValueUnformatted > $ctrl.pastValueUnformatted, 'negative-evolution': $ctrl.metricValueUnformatted < $ctrl.pastValueUnformatted}">
|
||||
{{ $ctrl.metricChangePercent }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,279 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <piwik-single-metric-view>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').component('piwikSingleMetricView', {
|
||||
templateUrl: 'plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.html?cb=' + piwik.cacheBuster,
|
||||
bindings: {
|
||||
metric: '<',
|
||||
idGoal: '<',
|
||||
metricTranslations: '<',
|
||||
metricDocumentations: '<',
|
||||
goals: '<',
|
||||
goalMetrics: '<'
|
||||
},
|
||||
controller: SingleMetricViewController
|
||||
});
|
||||
|
||||
SingleMetricViewController.$inject = ['piwik', 'piwikApi', '$element', '$httpParamSerializer', '$compile', '$scope', 'piwikPeriods', '$q'];
|
||||
|
||||
function SingleMetricViewController(piwik, piwikApi, $element, $httpParamSerializer, $compile, $scope, piwikPeriods, $q) {
|
||||
var seriesPickerScope;
|
||||
|
||||
var vm = this;
|
||||
vm.metricValue = null;
|
||||
vm.isLoading = false;
|
||||
vm.metricTranslation = null;
|
||||
vm.metricDocumentation = null;
|
||||
vm.selectableColumns = [];
|
||||
vm.responses = null;
|
||||
vm.sparklineParams = {};
|
||||
vm.$onInit = $onInit;
|
||||
vm.$onChanges = $onChanges;
|
||||
vm.$onDestroy = $onDestroy;
|
||||
vm.getCurrentPeriod = getCurrentPeriod;
|
||||
vm.getMetricTranslation = getMetricTranslation;
|
||||
vm.setMetric = setMetric;
|
||||
|
||||
function setSparklineParams() {
|
||||
var params = { module: 'API', action: 'get', columns: vm.metric };
|
||||
if (isIdGoalSet()) {
|
||||
params.idGoal = vm.idGoal;
|
||||
params.module = 'Goals';
|
||||
}
|
||||
vm.sparklineParams = params;
|
||||
}
|
||||
|
||||
function $onInit() {
|
||||
vm.selectedColumns = [vm.metric];
|
||||
if (piwik.period !== 'range') {
|
||||
vm.pastPeriod = getPastPeriodStr();
|
||||
}
|
||||
|
||||
setSelectableColumns();
|
||||
|
||||
createSeriesPicker();
|
||||
|
||||
$element.closest('.widgetContent')
|
||||
.on('widget:destroy', function() { $scope.$parent.$destroy(); })
|
||||
.on('widget:reload', function() { $scope.$parent.$destroy(); });
|
||||
|
||||
setSparklineParams();
|
||||
}
|
||||
|
||||
function $onChanges(changes) {
|
||||
if (changes.metric && changes.metric.previousValue !== changes.metric.currentValue) {
|
||||
onMetricChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function $onDestroy() {
|
||||
$element.closest('.widgetContent').off('widget:destroy').off('widget:reload');
|
||||
destroySeriesPicker();
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
vm.isLoading = true;
|
||||
|
||||
var promises = [];
|
||||
|
||||
var apiModule = 'API';
|
||||
var apiAction = 'get';
|
||||
|
||||
var extraParams = {};
|
||||
if (isIdGoalSet()) {
|
||||
extraParams.idGoal = vm.idGoal;
|
||||
// the conversion rate added by the AddColumnsProcessedMetrics filter conflicts w/ the goals one, so don't run it
|
||||
extraParams.filter_add_columns_when_show_all_columns = 0;
|
||||
|
||||
apiModule = 'Goals';
|
||||
apiAction = 'get';
|
||||
}
|
||||
|
||||
// first request for formatted data
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
format_metrics: 'all'
|
||||
}, extraParams)));
|
||||
|
||||
if (piwik.period !== 'range') {
|
||||
// second request for unformatted data so we can calculate evolution
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
format_metrics: '0'
|
||||
}, extraParams)));
|
||||
|
||||
// third request for past data (unformatted)
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
date: getLastPeriodDate(),
|
||||
format_metrics: '0',
|
||||
}, extraParams)));
|
||||
|
||||
// fourth request for past data (formatted for tooltip display)
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
date: getLastPeriodDate(),
|
||||
format_metrics: 'all',
|
||||
}, extraParams)));
|
||||
}
|
||||
|
||||
return $q.all(promises).then(function (responses) {
|
||||
vm.responses = responses;
|
||||
vm.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function recalculateValues() {
|
||||
// update display based on processed report metadata
|
||||
setWidgetTitle();
|
||||
vm.metricDocumentation = getMetricDocumentation();
|
||||
|
||||
// update data
|
||||
var currentData = vm.responses[0];
|
||||
vm.metricValue = currentData[vm.metric] || 0;
|
||||
|
||||
if (vm.responses[1]) {
|
||||
vm.metricValueUnformatted = vm.responses[1][vm.metric];
|
||||
|
||||
var pastData = vm.responses[2];
|
||||
vm.pastValueUnformatted = pastData[vm.metric] || 0;
|
||||
|
||||
var evolution = piwik.helper.calculateEvolution(vm.metricValueUnformatted, vm.pastValueUnformatted);
|
||||
vm.metricChangePercent = (evolution * 100).toFixed(2) + ' %';
|
||||
|
||||
var pastDataFormatted = vm.responses[3];
|
||||
vm.pastValue = pastDataFormatted[vm.metric] || 0;
|
||||
} else {
|
||||
vm.pastValue = null;
|
||||
vm.metricChangePercent = null;
|
||||
}
|
||||
|
||||
// don't change the metric translation until data is fetched to avoid loading state confusion
|
||||
vm.metricTranslation = getMetricTranslation();
|
||||
}
|
||||
|
||||
function getLastPeriodDate() {
|
||||
var RangePeriod = piwikPeriods.get('range');
|
||||
var result = RangePeriod.getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
|
||||
return $.datepicker.formatDate('yy-mm-dd', result);
|
||||
}
|
||||
|
||||
function setWidgetTitle() {
|
||||
var title = vm.getMetricTranslation();
|
||||
if (isIdGoalSet()) {
|
||||
var goalName = vm.goals[vm.idGoal].name;
|
||||
title = goalName + ' - ' + title;
|
||||
}
|
||||
|
||||
$element.closest('div.widget').find('.widgetTop > .widgetName > span').text(title);
|
||||
}
|
||||
|
||||
function getCurrentPeriod() {
|
||||
if (piwik.startDateString === piwik.endDateString) {
|
||||
return piwik.endDateString;
|
||||
}
|
||||
return piwik.startDateString + ', ' + piwik.endDateString;
|
||||
}
|
||||
|
||||
function createSeriesPicker() {
|
||||
vm.selectedColumns = [vm.idGoal ? ('goal' + vm.idGoal + '_' + vm.metric) : vm.metric];
|
||||
|
||||
var $widgetName = $element.closest('div.widget').find('.widgetTop > .widgetName');
|
||||
|
||||
var $seriesPicker = $('<piwik-series-picker class="single-metric-view-picker" multiselect="false" ' +
|
||||
'selectable-columns="$ctrl.selectableColumns" selectable-rows="[]" selected-columns="$ctrl.selectedColumns" ' +
|
||||
'selected-rows="[]" on-select="$ctrl.setMetric(columns[0])" />');
|
||||
|
||||
seriesPickerScope = $scope.$new();
|
||||
$compile($seriesPicker)(seriesPickerScope);
|
||||
|
||||
$widgetName.append($seriesPicker);
|
||||
}
|
||||
|
||||
function destroySeriesPicker() {
|
||||
$element.closest('div.widget').find('.single-metric-view-picker').remove();
|
||||
|
||||
seriesPickerScope.$destroy();
|
||||
seriesPickerScope = null;
|
||||
}
|
||||
|
||||
function getMetricDocumentation() {
|
||||
if (!vm.metricDocumentations || !vm.metricDocumentations[vm.metric]) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return vm.metricDocumentations[vm.metric];
|
||||
}
|
||||
|
||||
function getMetricTranslation() {
|
||||
if (!vm.metricTranslations || !vm.metricTranslations[vm.metric]) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return vm.metricTranslations[vm.metric];
|
||||
}
|
||||
|
||||
function setSelectableColumns() {
|
||||
var result = [];
|
||||
Object.keys(vm.metricTranslations).forEach(function (column) {
|
||||
result.push({ column: column, translation: vm.metricTranslations[column] });
|
||||
});
|
||||
|
||||
Object.keys(vm.goals).forEach(function (idgoal) {
|
||||
var goal = vm.goals[idgoal];
|
||||
vm.goalMetrics.forEach(function (column) {
|
||||
result.push({
|
||||
column: 'goal' + goal.idgoal + '_' + column,
|
||||
translation: goal.name + ' - ' + vm.metricTranslations[column]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
vm.selectableColumns = result;
|
||||
}
|
||||
|
||||
function onMetricChanged() {
|
||||
setSparklineParams();
|
||||
|
||||
fetchData().then(recalculateValues);
|
||||
|
||||
// notify widget of parameter change so it is replaced
|
||||
$element.closest('[widgetId]').trigger('setParameters', { column: vm.metric, idGoal: vm.idGoal });
|
||||
}
|
||||
|
||||
function setMetric(newColumn) {
|
||||
var idGoal;
|
||||
|
||||
var m = newColumn.match(/^goal([0-9]+)_(.*)/);
|
||||
if (m) {
|
||||
idGoal = +m[1];
|
||||
newColumn = m[2];
|
||||
}
|
||||
|
||||
if (vm.metric !== newColumn || idGoal !== vm.idGoal) {
|
||||
vm.metric = newColumn;
|
||||
vm.idGoal = idGoal;
|
||||
onMetricChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function getPastPeriodStr() {
|
||||
var startDate = piwikPeriods.get('range').getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
|
||||
var dateRange = piwikPeriods.get(piwik.period).parse(startDate).getDateRange();
|
||||
return $.datepicker.formatDate('yy-mm-dd', dateRange[0]) + ',' + $.datepicker.formatDate('yy-mm-dd', dateRange[1]);
|
||||
}
|
||||
|
||||
function isIdGoalSet() {
|
||||
return vm.idGoal || vm.idGoal === 0;
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,61 @@
|
||||
.singleMetricView {
|
||||
margin: 5px 12px 10px;
|
||||
display: inline-block;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
vertical-align: top;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.metric-sparkline {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
img {
|
||||
width: 100px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.metricEvolution {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
|
||||
&:not(.positive-evolution):not(.negative-evolution) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.positive-evolution::before {
|
||||
content: "";
|
||||
background: url(plugins/MultiSites/images/arrow_up.png) no-repeat center center;
|
||||
display: inline-block;
|
||||
height: 7px;
|
||||
width: 12px;
|
||||
}
|
||||
.negative-evolution::before {
|
||||
content: "";
|
||||
background: url(plugins/MultiSites/images/arrow_down.png) no-repeat center center;
|
||||
display: inline-block;
|
||||
height: 7px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[piwik-single-metric-view] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.single-metric-view-picker {
|
||||
margin-left: 6px;
|
||||
}
|
1236
msd2/tracking/piwik/plugins/CoreVisualizations/javascripts/jqplot.js
Normal file
1236
msd2/tracking/piwik/plugins/CoreVisualizations/javascripts/jqplot.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* DataTable UI class for JqplotGraph/Bar.
|
||||
*
|
||||
* @link http://www.jqplot.com
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
var exports = require('piwik/UI'),
|
||||
JqplotGraphDataTable = exports.JqplotGraphDataTable;
|
||||
|
||||
exports.JqplotBarGraphDataTable = function (element) {
|
||||
JqplotGraphDataTable.call(this, element);
|
||||
};
|
||||
|
||||
$.extend(exports.JqplotBarGraphDataTable.prototype, JqplotGraphDataTable.prototype, {
|
||||
|
||||
_setJqplotParameters: function (params) {
|
||||
JqplotGraphDataTable.prototype._setJqplotParameters.call(this, params);
|
||||
|
||||
this.jqplotParams.seriesDefaults = {
|
||||
renderer: $.jqplot.BarRenderer,
|
||||
rendererOptions: {
|
||||
shadowOffset: 1,
|
||||
shadowDepth: 2,
|
||||
shadowAlpha: .2,
|
||||
fillToZero: true,
|
||||
barMargin: this.data[0].length > 10 ? 2 : 10
|
||||
}
|
||||
};
|
||||
|
||||
this.jqplotParams.piwikTicks = {
|
||||
showTicks: true,
|
||||
showGrid: false,
|
||||
showHighlight: false,
|
||||
tickColor: this.tickColor
|
||||
};
|
||||
|
||||
this.jqplotParams.axes.xaxis.renderer = $.jqplot.CategoryAxisRenderer;
|
||||
this.jqplotParams.axes.xaxis.tickOptions = {
|
||||
showGridline: false
|
||||
};
|
||||
|
||||
this.jqplotParams.canvasLegend = {
|
||||
show: true
|
||||
};
|
||||
},
|
||||
|
||||
_bindEvents: function () {
|
||||
this.setYTicks();
|
||||
JqplotGraphDataTable.prototype._bindEvents.call(this);
|
||||
},
|
||||
|
||||
_showDataPointTooltip: function (element, seriesIndex, valueIndex) {
|
||||
var value = this.formatY(this.data[seriesIndex][valueIndex], seriesIndex);
|
||||
var series = this.jqplotParams.series[seriesIndex].label;
|
||||
|
||||
var percentage = '';
|
||||
if (typeof this.tooltip.percentages != 'undefined') {
|
||||
percentage = this.tooltip.percentages[seriesIndex][valueIndex];
|
||||
percentage = ' (' + NumberFormatter.formatPercent(percentage) + ')';
|
||||
}
|
||||
|
||||
var label = this.jqplotParams.axes.xaxis.labels[valueIndex];
|
||||
var text = '<strong>' + value + '</strong> ' + series + percentage;
|
||||
$(element).tooltip({
|
||||
track: true,
|
||||
items: '*',
|
||||
content: '<h3>' + label + '</h3>' + text,
|
||||
show: false,
|
||||
hide: false
|
||||
}).trigger('mouseover');
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, require);
|
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* DataTable UI class for JqplotGraph/Evolution.
|
||||
*
|
||||
* @link http://www.jqplot.com
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
var exports = require('piwik/UI'),
|
||||
JqplotGraphDataTable = exports.JqplotGraphDataTable,
|
||||
JqplotGraphDataTablePrototype = JqplotGraphDataTable.prototype;
|
||||
|
||||
exports.JqplotEvolutionGraphDataTable = function (element) {
|
||||
JqplotGraphDataTable.call(this, element);
|
||||
};
|
||||
|
||||
$.extend(exports.JqplotEvolutionGraphDataTable.prototype, JqplotGraphDataTablePrototype, {
|
||||
|
||||
_setJqplotParameters: function (params) {
|
||||
JqplotGraphDataTablePrototype._setJqplotParameters.call(this, params);
|
||||
|
||||
var defaultParams = {
|
||||
axes: {
|
||||
xaxis: {
|
||||
pad: 1.0,
|
||||
renderer: $.jqplot.CategoryAxisRenderer,
|
||||
tickOptions: {
|
||||
showGridline: false
|
||||
}
|
||||
}
|
||||
},
|
||||
piwikTicks: {
|
||||
showTicks: true,
|
||||
showGrid: true,
|
||||
showHighlight: true,
|
||||
tickColor: this.tickColor
|
||||
}
|
||||
};
|
||||
|
||||
if (this.props.show_line_graph) {
|
||||
defaultParams.seriesDefaults = {
|
||||
lineWidth: 1,
|
||||
markerOptions: {
|
||||
style: "filledCircle",
|
||||
size: 6,
|
||||
shadow: false
|
||||
}
|
||||
};
|
||||
} else {
|
||||
defaultParams.seriesDefaults = {
|
||||
renderer: $.jqplot.BarRenderer,
|
||||
rendererOptions: {
|
||||
shadowOffset: 1,
|
||||
shadowDepth: 2,
|
||||
shadowAlpha: .2,
|
||||
fillToZero: true,
|
||||
barMargin: this.data[0].length > 10 ? 2 : 10
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var overrideParams = {
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
canvasLegend: {
|
||||
show: true
|
||||
}
|
||||
};
|
||||
this.jqplotParams = $.extend(true, {}, defaultParams, this.jqplotParams, overrideParams);
|
||||
},
|
||||
|
||||
_bindEvents: function () {
|
||||
JqplotGraphDataTablePrototype._bindEvents.call(this);
|
||||
|
||||
var self = this;
|
||||
var lastTick = false;
|
||||
|
||||
$('#' + this.targetDivId)
|
||||
.on('jqplotMouseLeave', function (e, s, i, d) {
|
||||
$(this).css('cursor', 'default');
|
||||
JqplotGraphDataTablePrototype._destroyDataPointTooltip.call(this, $(this));
|
||||
})
|
||||
.on('jqplotClick', function (e, s, i, d) {
|
||||
if (lastTick !== false && typeof self.jqplotParams.axes.xaxis.onclick != 'undefined'
|
||||
&& typeof self.jqplotParams.axes.xaxis.onclick[lastTick] == 'string') {
|
||||
var url = self.jqplotParams.axes.xaxis.onclick[lastTick];
|
||||
|
||||
broadcast.propagateNewPage(url);
|
||||
}
|
||||
})
|
||||
.on('jqplotPiwikTickOver', function (e, tick) {
|
||||
lastTick = tick;
|
||||
var label;
|
||||
if (typeof self.jqplotParams.axes.xaxis.labels != 'undefined') {
|
||||
label = self.jqplotParams.axes.xaxis.labels[tick];
|
||||
} else {
|
||||
label = self.jqplotParams.axes.xaxis.ticks[tick];
|
||||
}
|
||||
|
||||
var text = [];
|
||||
for (var d = 0; d < self.data.length; d++) {
|
||||
var value = self.formatY(self.data[d][tick], d);
|
||||
var series = self.jqplotParams.series[d].label;
|
||||
text.push('<strong>' + value + '</strong> ' + piwikHelper.htmlEntities(series));
|
||||
}
|
||||
var content = '<h3>'+piwikHelper.htmlEntities(label)+'</h3>'+text.join('<br />');
|
||||
|
||||
$(this).tooltip({
|
||||
track: true,
|
||||
items: 'div',
|
||||
content: content,
|
||||
show: false,
|
||||
hide: false
|
||||
}).trigger('mouseover');
|
||||
if (typeof self.jqplotParams.axes.xaxis.onclick != 'undefined'
|
||||
&& typeof self.jqplotParams.axes.xaxis.onclick[lastTick] == 'string') {
|
||||
$(this).css('cursor', 'pointer');
|
||||
}
|
||||
});
|
||||
|
||||
this.setYTicks();
|
||||
},
|
||||
|
||||
_destroyDataPointTooltip: function () {
|
||||
// do nothing, tooltips are destroyed in the jqplotMouseLeave event
|
||||
},
|
||||
|
||||
render: function () {
|
||||
JqplotGraphDataTablePrototype.render.call(this);
|
||||
|
||||
if (initializeSparklines) {
|
||||
initializeSparklines();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, require);
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* DataTable UI class for JqplotGraph/Pie.
|
||||
*
|
||||
* @link http://www.jqplot.com
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
var exports = require('piwik/UI'),
|
||||
JqplotGraphDataTable = exports.JqplotGraphDataTable;
|
||||
|
||||
exports.JqplotPieGraphDataTable = function (element) {
|
||||
JqplotGraphDataTable.call(this, element);
|
||||
};
|
||||
|
||||
$.extend(exports.JqplotPieGraphDataTable.prototype, JqplotGraphDataTable.prototype, {
|
||||
|
||||
_setJqplotParameters: function (params) {
|
||||
JqplotGraphDataTable.prototype._setJqplotParameters.call(this, params);
|
||||
|
||||
this.jqplotParams.seriesDefaults = {
|
||||
renderer: $.jqplot.PieRenderer,
|
||||
rendererOptions: {
|
||||
shadow: false,
|
||||
showDataLabels: false,
|
||||
sliceMargin: 1,
|
||||
startAngle: 35
|
||||
}
|
||||
};
|
||||
|
||||
this.jqplotParams.piwikTicks = {
|
||||
showTicks: false,
|
||||
showGrid: false,
|
||||
showHighlight: false,
|
||||
tickColor: this.tickColor
|
||||
};
|
||||
|
||||
this.jqplotParams.legend = {
|
||||
show: false
|
||||
};
|
||||
this.jqplotParams.pieLegend = {
|
||||
show: true,
|
||||
labelColor: this.singleMetricColor
|
||||
};
|
||||
this.jqplotParams.canvasLegend = {
|
||||
show: true,
|
||||
singleMetric: true,
|
||||
singleMetricColor: this.singleMetricColor
|
||||
};
|
||||
|
||||
// pie charts have a different data format
|
||||
if (!(this.data[0][0] instanceof Array)) { // check if already in different format
|
||||
for (var i = 0; i < this.data[0].length; i++) {
|
||||
this.data[0][i] = [this.jqplotParams.axes.xaxis.ticks[i], this.data[0][i]];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_showDataPointTooltip: function (element, seriesIndex, valueIndex) {
|
||||
var value = this.formatY(this.data[0][valueIndex][1], 0);
|
||||
var series = this.jqplotParams.series[0].label;
|
||||
var percentage = this.tooltip.percentages[0][valueIndex];
|
||||
|
||||
var label = this.data[0][valueIndex][0];
|
||||
|
||||
var text = '<strong>' + NumberFormatter.formatPercent(percentage) + '</strong> (' + value + ' ' + series + ')';
|
||||
$(element).tooltip({
|
||||
track: true,
|
||||
items: '*',
|
||||
content: '<h3>' + label + '</h3>' + text,
|
||||
show: false,
|
||||
hide: false
|
||||
}).trigger('mouseover');
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, require);
|
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* Series Picker control addition for DataTable visualizations.
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
/**
|
||||
* This class creates and manages the Series Picker for certain DataTable visualizations.
|
||||
*
|
||||
* To add the series picker to your DataTable visualization, create a SeriesPicker instance
|
||||
* and after your visualization has been rendered, call the 'init' method.
|
||||
*
|
||||
* To customize SeriesPicker placement and behavior, you can bind callbacks to the following
|
||||
* events before calling 'init':
|
||||
*
|
||||
* 'placeSeriesPicker': Triggered after the DOM element for the series picker link is created.
|
||||
* You must use this event to add the link to the dataTable. YOu can also
|
||||
* use this event to position the link however you want.
|
||||
*
|
||||
* Callback Signature: function () {}
|
||||
*
|
||||
* 'seriesPicked': Triggered when the user selects one or more columns/rows.
|
||||
*
|
||||
* Callback Signature: function (eventInfo, columns, rows) {}
|
||||
*
|
||||
* Events are triggered via jQuery, so you bind callbacks to them like this:
|
||||
*
|
||||
* var picker = new SeriesPicker(dataTable);
|
||||
* $(picker).bind('placeSeriesPicker', function () {
|
||||
* $(this.domElem).doSomething(...);
|
||||
* });
|
||||
*
|
||||
* @param {dataTable} dataTable The dataTable instance to add a series picker to.
|
||||
* @constructor
|
||||
* @deprecated use the piwik-series-picker directive instead
|
||||
*/
|
||||
var SeriesPicker = function (dataTable) {
|
||||
this.domElem = null;
|
||||
this.dataTableId = dataTable.workingDivId;
|
||||
|
||||
// the columns that can be selected
|
||||
this.selectableColumns = dataTable.props.selectable_columns;
|
||||
|
||||
// the rows that can be selected
|
||||
this.selectableRows = dataTable.props.selectable_rows;
|
||||
|
||||
// render the picker?
|
||||
this.show = !! dataTable.props.show_series_picker
|
||||
&& (this.selectableColumns || this.selectableRows);
|
||||
|
||||
// can multiple rows we selected?
|
||||
this.multiSelect = !! dataTable.props.allow_multi_select_series_picker;
|
||||
};
|
||||
|
||||
SeriesPicker.prototype = {
|
||||
|
||||
/**
|
||||
* Initializes the series picker by creating the element. Must be called when
|
||||
* the datatable the picker is being attached to is ready for it to be drawn.
|
||||
*/
|
||||
init: function () {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var selectedColumns = this.selectableColumns
|
||||
.filter(isItemDisplayed)
|
||||
.map(function (columnConfig) {
|
||||
return columnConfig.column;
|
||||
});
|
||||
|
||||
var selectedRows = this.selectableRows
|
||||
.filter(isItemDisplayed)
|
||||
.map(function (rowConfig) {
|
||||
return rowConfig.matcher;
|
||||
});
|
||||
|
||||
// initialize dom element
|
||||
var seriesPicker = '<piwik-series-picker'
|
||||
+ ' multiselect="' + (this.multiSelect ? 'true' : 'false') + '"'
|
||||
+ ' selectable-columns="selectableColumns"'
|
||||
+ ' selectable-rows="selectableRows"'
|
||||
+ ' selected-columns="selectedColumns"'
|
||||
+ ' selected-rows="selectedRows"'
|
||||
+ ' on-select="selectionChanged(columns, rows)"/>';
|
||||
|
||||
this.domElem = $(seriesPicker);
|
||||
|
||||
$(this).trigger('placeSeriesPicker');
|
||||
|
||||
piwikHelper.compileAngularComponents(this.domElem, {
|
||||
params: {
|
||||
selectableColumns: this.selectableColumns,
|
||||
selectableRows: this.selectableRows,
|
||||
selectedColumns: selectedColumns,
|
||||
selectedRows: selectedRows,
|
||||
selectionChanged: function selectionChanged(columns, rows) {
|
||||
if (columns.length === 0 && rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
rows = rows.map(encodeURIComponent);
|
||||
|
||||
$(self).trigger('seriesPicked', [columns, rows]);
|
||||
|
||||
// inform dashboard widget about changed parameters (to be restored on reload)
|
||||
var UI = require('piwik/UI');
|
||||
var params = {
|
||||
columns: columns,
|
||||
columns_to_display: columns,
|
||||
rows: rows,
|
||||
rows_to_display: rows
|
||||
};
|
||||
|
||||
var tableNode = $('#' + self.dataTableId);
|
||||
UI.DataTable.prototype.notifyWidgetParametersChange(tableNode, params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function isItemDisplayed(columnOrRowConfig) {
|
||||
return columnOrRowConfig.displayed;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the translation of a metric that can be selected.
|
||||
*
|
||||
* @param {String} metric The name of the metric, ie, 'nb_visits' or 'nb_actions'.
|
||||
* @return {String} The metric translation. If one cannot be found, the metric itself
|
||||
* is returned.
|
||||
*/
|
||||
getMetricTranslation: function (metric) {
|
||||
for (var i = 0; i !== this.selectableColumns.length; ++i) {
|
||||
if (this.selectableColumns[i].column === metric) {
|
||||
return this.selectableColumns[i].translation;
|
||||
}
|
||||
}
|
||||
return metric;
|
||||
}
|
||||
};
|
||||
|
||||
var exports = require('piwik/DataTableVisualizations/Widgets');
|
||||
exports.SeriesPicker = SeriesPicker;
|
||||
|
||||
})(jQuery, require);
|
@ -0,0 +1,26 @@
|
||||
|
||||
.piwik-graph {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.dataTableVizEvolution {
|
||||
> .dataTableWrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.piwik-graph {
|
||||
height: 170px;
|
||||
}
|
||||
}
|
||||
|
||||
.widget .dataTableVizEvolution {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.widget .tagCloud {
|
||||
padding: 10px;
|
||||
}
|
||||
.widget .dataTableVizBar .jqplot-graph {
|
||||
padding: 0 10px 10px 10px;
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
.jqplot-loading {
|
||||
background: url(../../Morpheus/images/loading-blue.gif) no-repeat center center white;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.jqplot-target {
|
||||
position: relative;
|
||||
color: #333;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.jqplot-axis {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.jqplot-xaxis {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.jqplot-yaxis {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.jqplot-y2axis,
|
||||
.jqplot-y3axis {
|
||||
margin: 0 3px 0 7px;
|
||||
}
|
||||
|
||||
.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-xaxis-tick {
|
||||
top: 0;
|
||||
left: 15px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jqplot-yaxis-tick {
|
||||
right: 0;
|
||||
top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.jqplot-yaxis-tick.jqplot-breakTick {
|
||||
right: -20px;
|
||||
margin-right: 0;
|
||||
padding: 1px 5px 1px 5px;
|
||||
z-index: 2;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.jqplot-xaxis-label {
|
||||
margin-top: 10px;
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-yaxis-label {
|
||||
margin-right: 10px;
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-title {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-bottom: 0.5em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
/**
|
||||
* ROW EVOLUTION POPUP
|
||||
*/
|
||||
|
||||
.rowevolution {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#Piwik_Popover .rowevolution .alert-info {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
a.rowevolution-startmulti {
|
||||
color: #4183C4 !important;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.rowevolution h2 {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.rowevolution .metrics-container {
|
||||
padding: 11px 0 5px 0;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics {
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.multirowevolution table.metrics {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics,
|
||||
.multirowevolution table.metrics {
|
||||
/* prevent select for shift-click on metric toggles */
|
||||
user-select: none; /* CSS3 */
|
||||
-moz-user-select: none; /* Gecko (Firefox) */
|
||||
-khtml-user-select: none; /* Webkit (Safari, Chrome) */
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.sparkline,
|
||||
.multirowevolution table.metrics td.sparkline {
|
||||
float:none;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics tr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td {
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 4px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.sparkline {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.multirowevolution table.metrics td.sparkline {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
/** IE7 does not support inline image data, which is needed for spark lines */
|
||||
*+html .multirowevolution table.metrics td.sparkline,
|
||||
*+html .rowevolution table.metrics td.sparkline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.text {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #7E7363;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.text img {
|
||||
max-width: 16px;
|
||||
max-height: 16px;
|
||||
}
|
||||
|
||||
.multirowevolution table.metrics td.text {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.text span.details {
|
||||
font-weight: normal;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.text span.change {
|
||||
display: block;
|
||||
float: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.text span.good {
|
||||
color: #43a047;
|
||||
}
|
||||
|
||||
.rowevolution table.metrics td.text span.bad {
|
||||
color: #D4291F;
|
||||
}
|
||||
|
||||
.rowevolution .metric-selectbox,
|
||||
.rowevolution .compare-container {
|
||||
padding: 15px 0 5px 0;
|
||||
}
|
||||
|
||||
.rowevolution .metric-selectbox select {
|
||||
font-size: 13px;
|
||||
color: #444;
|
||||
margin: 8px 0 0 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* SERIES PICKER FOR CHARTS
|
||||
*/
|
||||
|
||||
.jqplot-seriespicker {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
width: 24px;
|
||||
height: 16px;
|
||||
margin-top: 3px;
|
||||
background: url(../../Morpheus/images/chart_line_edit.png) no-repeat center center;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1010; /* must be above ui dialog */
|
||||
margin-top: -2px;
|
||||
background: url(../../Morpheus/images/chart_line_edit.png) no-repeat 7px 4px #f7f7f7;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
border: 1px solid #e4e5e4;
|
||||
padding: 6px 9px;
|
||||
border-radius: 4px;
|
||||
-moz-box-shadow: 1px 1px 2px #666;
|
||||
-webkit-box-shadow: 1px 1px 2px #666;
|
||||
box-shadow: 1px 1px 2px #666;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover p {
|
||||
margin: 0;
|
||||
padding: 0 4px 0 0;
|
||||
line-height: 15px;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover p.headline {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
padding: 0 0 6px 22px;
|
||||
color: #7E7363;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover p.headline.recordsToPlot {
|
||||
padding: 8px 0 3px 0;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover.alignright p.headline {
|
||||
padding: 0 22px 6px 0;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover input.select {
|
||||
margin-right: 8px;
|
||||
min-height: 5px;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover p.pickColumn,
|
||||
.jqplot-seriespicker-popover p.pickRow {
|
||||
cursor: pointer;
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
{%- set subtablesAreDisabled = properties.show_goals_columns|default(false)
|
||||
and properties.disable_subtable_when_show_goals|default(false) -%}
|
||||
{%- set showingEmbeddedSubtable = properties.show_embedded_subtable is not empty
|
||||
and idSubtable|default(false) -%}
|
||||
{% if error is defined %}
|
||||
{{ error.message }}
|
||||
{% else %}
|
||||
{%- if not showingEmbeddedSubtable -%}
|
||||
<table cellspacing="0" class="dataTable">
|
||||
{% include "@CoreHome/_dataTableHead.twig" %}
|
||||
|
||||
<tbody>
|
||||
{%- endif -%}
|
||||
{% if showingEmbeddedSubtable and dataTable.getRowsCount() == 0 %}
|
||||
{% if clientSideParameters is not defined or clientSideParameters.filter_pattern_recursive is not defined or not clientSideParameters.filter_pattern_recursive %}
|
||||
<tr class="nodata">
|
||||
<td colspan="{{ properties.columns_to_display|length }}">{{ 'CoreHome_CategoryNoData'|translate }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{%- for rowId, row in dataTable.getRows() -%}
|
||||
{%- set rowHasSubtable = not subtablesAreDisabled and row.getIdSubDataTable() and properties.subtable_controller_action is not null -%}
|
||||
{%- set rowSubtableId = row.getMetadata('idsubdatatable_in_db')|default(row.getIdSubDataTable()) -%}
|
||||
{%- set isSummaryRow = rowId == constant('Piwik\\DataTable::ID_SUMMARY_ROW') -%}
|
||||
{%- set shouldHighlightRow = isSummaryRow and properties.highlight_summary_row -%}
|
||||
|
||||
{# display this row if it doesn't have a subtable or if we don't replace the row with the subtable #}
|
||||
{%- set showRow = subtablesAreDisabled
|
||||
or not rowHasSubtable
|
||||
or not properties.show_expanded|default(false)
|
||||
or not properties.replace_row_with_subtable|default(false) -%}
|
||||
|
||||
{% if showRow %}
|
||||
<tr {% if rowHasSubtable %}id="{{ rowSubtableId }}"{% endif %}
|
||||
{% if row.getMetadata('segment') is not false %} data-segment-filter="{{ row.getMetadata('segment')|e('html_attr') }}"{% endif %}
|
||||
{% if row.getMetadata('url') is not false %} data-url-label="{{ row.getMetadata('url')|rawSafeDecoded }}"{% endif %}
|
||||
data-row-metadata="{{ row.getMetadata|json_encode|e('html_attr') }}"
|
||||
class="{{ row.getMetadata('css_class') }} {% if rowHasSubtable %}subDataTable{% endif %}{% if shouldHighlightRow %} highlight{% endif %}{% if isSummaryRow %} summaryRow{% endif %}"
|
||||
{% if rowHasSubtable %}title="{{ 'CoreHome_ClickRowToExpandOrContract'|translate }}"{% endif %}>
|
||||
{% for column in properties.columns_to_display %}
|
||||
{% set cellAttributes = visualization.getCellHtmlAttributes(row, column) %}
|
||||
<td {% if cellAttributes is not empty %}{% for name, value in cellAttributes %}{{ name|e('html') }}="{{ value|e('html_attr') }}" {% endfor %}{% endif %}>
|
||||
{% include "@CoreHome/_dataTableCell.twig" with properties %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{# display subtable if present and showing expanded datatable #}
|
||||
{% if properties.show_expanded|default(false) and rowHasSubtable %}
|
||||
{% include "@CoreVisualizations/_dataTableViz_htmlTable.twig" with {'dataTable': row.getSubtable(), 'idSubtable': rowSubtableId} %}
|
||||
{% endif %}
|
||||
{%- endfor -%}
|
||||
{% if dataTable.getTotalsRow and properties.show_totals_row %}
|
||||
{% set row = dataTable.getTotalsRow %}
|
||||
{% set rowId = 'totalsRow' %}
|
||||
<tr class="{{ row.getMetadata('css_class') }} totalsRow"
|
||||
title="Total values for this table">
|
||||
{% for column in properties.columns_to_display %}
|
||||
<td>
|
||||
{% include "@CoreHome/_dataTableCell.twig" with properties %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- if not showingEmbeddedSubtable -%}
|
||||
</tbody>
|
||||
</table>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
@ -0,0 +1,3 @@
|
||||
<div alt="{{ 'Mobile_StaticGraph'|translate }}" class="jqplot-graph">
|
||||
<div class="piwik-graph" data-data="{{ visualization.getGraphData(dataTable, properties)|json_encode }}"></div>
|
||||
</div>
|
@ -0,0 +1,48 @@
|
||||
{% import '@CoreVisualizations/macros.twig' as macros %}
|
||||
|
||||
{% if not isWidget %}
|
||||
<div class="card"><div class="card-content">
|
||||
{% endif %}
|
||||
{% if title is not empty %}<h2 class="card-title"
|
||||
{% if titleAttributes is not empty %}{% for attribute, value in titleAttributes %}{{ attribute }}="{{ value }}"{% endfor %}{% endif %}
|
||||
>{{ title }}</h2>{% endif %}
|
||||
{% if not isWidget %}
|
||||
<div class="row">
|
||||
<div class="col m6">
|
||||
{% endif %}
|
||||
|
||||
{% for key, sparkline in sparklines %}
|
||||
{% if key is even %}
|
||||
{{ macros.singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if not isWidget %}
|
||||
<br style="clear:left"/>
|
||||
</div>
|
||||
<div class="col m6">
|
||||
{% endif %}
|
||||
|
||||
{% for key, sparkline in sparklines %}
|
||||
{% if key is odd %}
|
||||
{{ macros.singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<br style="clear:left"/>
|
||||
|
||||
{% if not isWidget %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if areSparklinesLinkable %}
|
||||
{% include "_sparklineFooter.twig" %}
|
||||
{% endif %}
|
||||
|
||||
{% if footerMessage is not empty %}
|
||||
<div class='datatableFooterMessage'>{{ footerMessage | raw }}</div>
|
||||
{% endif %}
|
||||
{% if not isWidget %}
|
||||
</div></div>
|
||||
{% endif %}
|
@ -0,0 +1,17 @@
|
||||
<div class="tagCloud">
|
||||
{% for word,value in cloudValues %}
|
||||
<span title="{{ value.word|rawSafeDecoded }} ({{ value.value }} {{ properties.translations[cloudColumn]|default(cloudColumn) }})" class="word size{{ value.size }}
|
||||
{# we strike tags with 0 hits #}
|
||||
{% if value.value == 0 %}valueIsZero{% endif %}">
|
||||
{% if labelMetadata[value.word].url is not sameas(false) %}
|
||||
<a href="{{ labelMetadata[value.word].url }}" rel="noreferrer noopener" target="_blank">
|
||||
{% endif %}
|
||||
{% if labelMetadata[value.word].logo is not sameas(false) %}
|
||||
<img src="{{ labelMetadata[value.word].logo }}" width="{{ value.logoWidth }}" />
|
||||
{% else %}
|
||||
{{ value.wordTruncated|rawSafeDecoded }}
|
||||
{% endif %}
|
||||
{% if labelMetadata[value.word].url is not sameas(false) %}</a>{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
@ -0,0 +1,37 @@
|
||||
{% macro singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) %}
|
||||
|
||||
<div class="sparkline {% if areSparklinesLinkable is defined and not areSparklinesLinkable %}notLinkable{% endif %}">
|
||||
{% if sparkline.url %}{{ sparkline(sparkline.url)|raw }}{% endif %}
|
||||
<div>
|
||||
{% for metric in sparkline.metrics %}
|
||||
<span {% if allMetricsDocumentation[metric.column] is defined and allMetricsDocumentation[metric.column] %}title="{{ allMetricsDocumentation[metric.column] }}"{% endif %}>
|
||||
{% if '%s' in metric.description -%}
|
||||
{{ metric.description|translate("<strong>"~metric.value~"</strong>")|raw }}
|
||||
{%- else %}
|
||||
<strong>{{ metric.value }}</strong> {{ metric.description }}
|
||||
{%- endif %}{% if not loop.last %}, {% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% if sparkline.evolution is defined %}
|
||||
|
||||
{% set evolutionPretty = sparkline.evolution.percent %}
|
||||
|
||||
{% if sparkline.evolution.percent < 0 %}
|
||||
{% set evolutionClass = 'negative-evolution' %}
|
||||
{% set evolutionIcon = 'arrow_down.png' %}
|
||||
{% elseif sparkline.evolution.percent == 0 %}
|
||||
{% set evolutionClass = 'neutral-evolution' %}
|
||||
{% set evolutionIcon = 'stop.png' %}
|
||||
{% else %}
|
||||
{% set evolutionClass = 'positive-evolution' %}
|
||||
{% set evolutionIcon = 'arrow_up.png' %}
|
||||
{% set evolutionPretty = '+' ~ sparkline.evolution.percent %}
|
||||
{% endif %}
|
||||
|
||||
<span class="metricEvolution" title="{{ sparkline.evolution.tooltip }}"><img
|
||||
style="padding-right:4px" src="plugins/MultiSites/images/{{ evolutionIcon }}"/>
|
||||
<strong class="{{ evolutionClass }}">{{ evolutionPretty }}</strong></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
Reference in New Issue
Block a user