Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/CoreVisualizations')
-rw-r--r--plugins/CoreVisualizations/JqplotDataGenerator.php142
-rw-r--r--plugins/CoreVisualizations/JqplotDataGenerator/Chart.php46
-rw-r--r--plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php191
-rw-r--r--plugins/CoreVisualizations/Visualizations/Graph.php8
-rw-r--r--plugins/CoreVisualizations/Visualizations/HtmlTable.php25
-rw-r--r--plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php7
-rw-r--r--plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php41
-rw-r--r--plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php2
-rw-r--r--plugins/CoreVisualizations/Visualizations/Sparkline.php102
-rw-r--r--plugins/CoreVisualizations/Visualizations/Sparklines.php170
-rw-r--r--plugins/CoreVisualizations/Visualizations/Sparklines/Config.php110
-rw-r--r--plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.js4
-rw-r--r--plugins/CoreVisualizations/javascripts/jqplot.js30
-rw-r--r--plugins/CoreVisualizations/javascripts/jqplotBarGraph.js17
-rw-r--r--plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js54
-rw-r--r--plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less14
-rw-r--r--plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig25
-rw-r--r--plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_comparisons.twig83
-rw-r--r--plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_ratio.twig26
-rw-r--r--plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig31
-rw-r--r--plugins/CoreVisualizations/templates/macros.twig72
-rw-r--r--plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php193
-rw-r--r--plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php35
23 files changed, 1233 insertions, 195 deletions
diff --git a/plugins/CoreVisualizations/JqplotDataGenerator.php b/plugins/CoreVisualizations/JqplotDataGenerator.php
index d725d22b04..e120764005 100644
--- a/plugins/CoreVisualizations/JqplotDataGenerator.php
+++ b/plugins/CoreVisualizations/JqplotDataGenerator.php
@@ -10,11 +10,16 @@
namespace Piwik\Plugins\CoreVisualizations;
use Exception;
+use Piwik\API\Request;
use Piwik\Common;
use Piwik\DataTable;
+use Piwik\DataTable\Row;
use Piwik\Metrics;
+use Piwik\Period\Factory;
use Piwik\Piwik;
+use Piwik\Plugins\API\Filter\DataComparisonFilter;
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator\Chart;
+use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php';
@@ -26,7 +31,7 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/JqplotDataGenerat
class JqplotDataGenerator
{
/**
- * View properties. @see Piwik\ViewDataTable for more info.
+ * View properties. @see \Piwik\ViewDataTable for more info.
*
* @var array
*/
@@ -34,6 +39,15 @@ class JqplotDataGenerator
protected $graphType;
+ protected $isComparing;
+
+ private $availableSegments;
+
+ /**
+ * @var JqplotGraph
+ */
+ protected $graph;
+
/**
* Creates a new JqplotDataGenerator instance for a graph type and view properties.
*
@@ -42,14 +56,14 @@ class JqplotDataGenerator
* @throws \Exception
* @return JqplotDataGenerator
*/
- public static function factory($type, $properties)
+ public static function factory($type, $properties, JqplotGraph $graph)
{
switch ($type) {
case 'evolution':
- return new JqplotDataGenerator\Evolution($properties, $type);
+ return new JqplotDataGenerator\Evolution($properties, $type, $graph);
case 'pie':
case 'bar':
- return new JqplotDataGenerator($properties, $type);
+ return new JqplotDataGenerator($properties, $type, $graph);
default:
throw new Exception("Unknown JqplotDataGenerator type '$type'.");
}
@@ -63,10 +77,14 @@ class JqplotDataGenerator
*
* @internal param \Piwik\Plugin\ViewDataTable $visualization
*/
- public function __construct($properties, $graphType)
+ public function __construct($properties, $graphType, JqplotGraph $graph)
{
$this->properties = $properties;
$this->graphType = $graphType;
+ $this->isComparing = $graph->isComparing();
+ $this->graph = $graph;
+
+ $this->availableSegments = Request::processRequest('SegmentEditor.getAll', $override = [], $default = []);
}
/**
@@ -80,12 +98,6 @@ class JqplotDataGenerator
$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);
}
@@ -101,47 +113,123 @@ class JqplotDataGenerator
{
$xLabels = $dataTable->getColumn('label');
- $columnNames = $this->properties['columns_to_display'];
- if (($labelColumnIndex = array_search('label', $columnNames)) !== false) {
- unset($columnNames[$labelColumnIndex]);
+ $columnsToDisplay = array_values($this->properties['columns_to_display']);
+ if (($labelColumnIndex = array_search('label', $columnsToDisplay)) !== false) {
+ unset($columnsToDisplay[$labelColumnIndex]);
}
- $columnNameToTranslation = $columnNameToValue = array();
- foreach ($columnNames as $columnName) {
- $columnNameToTranslation[$columnName] = @$this->properties['translations'][$columnName];
-
- $columnNameToValue[$columnName] = $dataTable->getColumn($columnName);
+ $seriesMetadata = null;
+ if ($this->isComparing) {
+ list($yLabels, $serieses, $seriesMetadata) = $this->getComparisonTableSerieses($dataTable, $columnsToDisplay);
+ } else {
+ list($yLabels, $serieses) = $this->getMainTableSerieses($dataTable, $columnsToDisplay);
}
$visualization->dataTable = $dataTable;
$visualization->properties = $this->properties;
$visualization->setAxisXLabels($xLabels);
- $visualization->setAxisYValues($columnNameToValue);
- $visualization->setAxisYLabels($columnNameToTranslation);
+ $visualization->setAxisYValues($serieses, $seriesMetadata);
+ $visualization->setAxisYLabels($yLabels);
- $units = $this->getUnitsForColumnsToDisplay();
+ $units = $this->getUnitsForSerieses($yLabels);
$visualization->setAxisYUnits($units);
}
- protected function getUnitsForColumnsToDisplay()
+ private function getMainTableSerieses(DataTable $dataTable, $columnNames)
+ {
+ $columnNameToTranslation = [];
+
+ foreach ($columnNames as $columnName) {
+ $columnNameToTranslation[$columnName] = @$this->properties['translations'][$columnName];
+ }
+
+ $columnNameToValue = array();
+ foreach ($columnNames as $columnName) {
+ $columnNameToValue[$columnName] = $dataTable->getColumn($columnName);
+ }
+
+ return [$columnNameToTranslation, $columnNameToValue];
+ }
+
+ private function getComparisonTableSerieses(DataTable $dataTable, $columnsToDisplay)
+ {
+ $seriesLabels = [];
+ $serieses = [];
+ $seriesMetadata = [];
+
+ $seriesIndices = [];
+
+ foreach ($dataTable->getRows() as $row) {
+ /** @var DataTable $comparisonTable */
+ $comparisonTable = $row->getComparisons();
+ if (empty($comparisonTable)) {
+ continue;
+ }
+
+ foreach ($comparisonTable->getRows() as $index => $compareRow) {
+ foreach ($columnsToDisplay as $columnIndex => $columnName) {
+ $seriesId = $columnName . '|' . $index;
+
+ if (!isset($seriesIndices[$seriesId])) {
+ $seriesIndices[$seriesId] = count($seriesIndices);
+ }
+
+ $seriesLabel = $this->getComparisonSeriesLabel($compareRow, $columnName);
+ $seriesLabels[$seriesId] = $seriesLabel;
+ $serieses[$seriesId][] = $compareRow->getColumn($columnName);
+
+ $seriesMetadata[$seriesId] = [
+ 'seriesIndex' => $seriesIndices[$seriesId],
+ 'metricIndex' => $columnIndex,
+ ];
+ }
+ }
+ }
+
+ return [$seriesLabels, $serieses, $seriesMetadata];
+ }
+
+ protected function getComparisonSeriesLabel(Row $compareRow, $columnName, $rowLabel = false)
+ {
+ return $this->getComparisonSeriesLabelFromCompareSeries($compareRow->getMetadata('compareSeriesPretty'), $columnName, $rowLabel);
+ }
+
+ protected function getComparisonSeriesLabelFromCompareSeries($compareSeriesPretty, $columnName, $rowLabel = false)
+ {
+ $columnTranslation = @$this->properties['translations'][$columnName];
+
+ if (empty($rowLabel)) {
+ $label = $columnTranslation;
+ } else {
+ $label = "$rowLabel ($columnTranslation)";
+ }
+
+ $label .= ' ' . $compareSeriesPretty;
+ return $label;
+ }
+
+ protected function getUnitsForSerieses($yLabels)
{
// derive units from column names
- $units = $this->deriveUnitsFromRequestedColumnNames();
+ $units = $this->deriveUnitsFromRequestedColumnNames($yLabels);
if (!empty($this->properties['y_axis_unit'])) {
$units = array_fill(0, count($units), $this->properties['y_axis_unit']);
}
return $units;
}
- private function deriveUnitsFromRequestedColumnNames()
+ private function deriveUnitsFromRequestedColumnNames($yLabels)
{
$idSite = Common::getRequestVar('idSite', null, 'int');
$units = array();
- foreach ($this->properties['columns_to_display'] as $columnName) {
+ foreach ($yLabels as $seriesId => $ignore) {
+ $parts = explode('|', $seriesId, 2);
+ $columnName = $parts[0];
+
$derivedUnit = Metrics::getUnit($columnName, $idSite);
- $units[$columnName] = empty($derivedUnit) ? false : $derivedUnit;
+ $units[$seriesId] = empty($derivedUnit) ? false : $derivedUnit;
}
return $units;
}
diff --git a/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php b/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php
index 44be4af356..2ac4b6f8d7 100644
--- a/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php
+++ b/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php
@@ -25,14 +25,16 @@ class Chart
// temporary
public $properties;
- public function setAxisXLabels($xLabels)
+ public function setAxisXLabels($xLabels, $xTicks = null, $index = 0)
{
+ $axisName = $this->getXAxis($index);
+
$xSteps = $this->properties['x_axis_step_size'];
$showAllTicks = $this->properties['show_all_ticks'];
- $this->axes['xaxis']['labels'] = array_values($xLabels);
+ $this->axes[$axisName]['labels'] = array_values($xLabels);
- $ticks = array_values($xLabels);
+ $ticks = array_values($xTicks ?: $xLabels);
if (!$showAllTicks) {
// unset labels so there are $xSteps number of blank ticks between labels
@@ -42,7 +44,7 @@ class Chart
}
}
}
- $this->axes['xaxis']['ticks'] = $ticks;
+ $this->axes[$axisName]['ticks'] = $ticks;
}
public function setAxisXOnClick(&$onClick)
@@ -50,14 +52,20 @@ class Chart
$this->axes['xaxis']['onclick'] = & $onClick;
}
- public function setAxisYValues(&$values)
+ public function setAxisYValues(&$values, $seriesMetadata = null)
{
foreach ($values as $label => &$data) {
- $this->series[] = array(
+ $seriesInfo = array(
'label' => $label,
- 'internalLabel' => $label
+ 'internalLabel' => $label,
);
+ if (isset($seriesMetadata[$label])) {
+ $seriesInfo = array_merge($seriesInfo, $seriesMetadata[$label]);
+ }
+
+ $this->series[] = $seriesInfo;
+
array_walk($data, function (&$v) {
$v = (float) Common::forceDotAsSeparatorForDecimalPoint($v);
});
@@ -116,4 +124,28 @@ class Chart
return $data;
}
+
+ public function setAxisXLabelsMultiple($xLabels, $seriesToXAxis, $ticks = null)
+ {
+ foreach ($xLabels as $index => $labels) {
+ $this->setAxisXLabels($labels, $ticks === null ? null : $ticks[$index], $index);
+ }
+
+ foreach ($seriesToXAxis as $seriesIndex => $xAxisIndex) {
+ $axisName = $this->getXAxis($xAxisIndex);
+
+ // don't actually set xaxis otherwise jqplot will show too many axes. however, we need the xaxis labels, so we add them
+ // to the jqplot config
+ $this->series[$seriesIndex]['_xaxis'] = $axisName;
+ }
+ }
+
+ private function getXAxis($index)
+ {
+ $axisName = 'xaxis';
+ if ($index != 0) {
+ $axisName = 'x' . ($index + 1) . 'axis';
+ }
+ return $axisName;
+ }
}
diff --git a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php
index cae2577965..814903265b 100644
--- a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php
+++ b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php
@@ -11,6 +11,13 @@ namespace Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
use Piwik\Archive\DataTableFactory;
use Piwik\Common;
use Piwik\DataTable;
+use Piwik\DataTable\DataTableInterface;
+use Piwik\DataTable\Row;
+use Piwik\Date;
+use Piwik\Metrics;
+use Piwik\Period;
+use Piwik\Period\Factory;
+use Piwik\Plugins\API\Filter\DataComparisonFilter;
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
use Piwik\Url;
@@ -19,6 +26,18 @@ use Piwik\Url;
*/
class Evolution extends JqplotDataGenerator
{
+ protected function getUnitsForColumnsToDisplay()
+ {
+ $idSite = Common::getRequestVar('idSite', null, 'int');
+
+ $units = [];
+ foreach ($this->properties['columns_to_display'] as $columnName) {
+ $derivedUnit = Metrics::getUnit($columnName, $idSite);
+ $units[$columnName] = empty($derivedUnit) ? false : $derivedUnit;
+ }
+ return $units;
+ }
+
/**
* @param DataTable|DataTable\Map $dataTable
* @param Chart $visualization
@@ -33,11 +52,16 @@ class Evolution extends JqplotDataGenerator
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"
- }
+ $dataTables = $dataTable->getDataTables();
+
+ // determine x labels based on both the displayed date range and the compared periods
+ /** @var Period[][] $xLabels */
+ $xLabels = [
+ [], // placeholder for first series
+ ];
+
+ $this->addComparisonXLabels($xLabels, reset($dataTables));
+ $this->addSelectedSeriesXLabels($xLabels, $dataTables);
$units = $this->getUnitsForColumnsToDisplay();
@@ -47,27 +71,37 @@ class Evolution extends JqplotDataGenerator
? : array(false) // make sure that a series is plotted even if there is no data
;
+ $columnsToDisplay = array_values($this->properties['columns_to_display']);
+
+ list($seriesMetadata, $seriesUnits, $seriesLabels, $seriesToXAxis) =
+ $this->getSeriesMetadata($rowsToDisplay, $columnsToDisplay, $units, $dataTables);
+
// 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];
+ foreach ($columnsToDisplay as $columnName) {
+ if (!$this->isComparing) {
+ $this->setNonComparisonSeriesData($allSeriesData, $rowLabel, $columnName, $dataTable);
+ } else {
+ $this->setComparisonSeriesData($allSeriesData, $seriesLabels, $rowLabel, $columnName, $dataTable);
+ }
}
}
$visualization->dataTable = $dataTable;
$visualization->properties = $this->properties;
- $visualization->setAxisXLabels($xLabels);
- $visualization->setAxisYValues($allSeriesData);
+ $visualization->setAxisYValues($allSeriesData, $seriesMetadata);
$visualization->setAxisYUnits($seriesUnits);
- $dataTables = $dataTable->getDataTables();
+ $xLabelStrs = [];
+ $xAxisTicks = [];
+ foreach ($xLabels as $index => $seriesXLabels) {
+ $xLabelStrs[$index] = array_map(function (Period $p) { return $p->getLocalizedLongString(); }, $seriesXLabels);
+ $xAxisTicks[$index] = array_map(function (Period $p) { return $p->getLocalizedShortString(); }, $seriesXLabels);
+ }
+
+ $visualization->setAxisXLabelsMultiple($xLabelStrs, $seriesToXAxis, $xAxisTicks);
if ($this->isLinkEnabled()) {
$idSite = Common::getRequestVar('idSite', null, 'int');
@@ -144,4 +178,131 @@ class Evolution extends JqplotDataGenerator
}
return $linkEnabled;
}
+
+ /**
+ * Each period comparison shows data over different data points than the main series (eg, 2014-02-03,1014-02-06 compared w/ 2015-03-04,2015-03-15).
+ * Though we only display the selected period's x labels, we need to both have the labels for all these data points for tooltips and to stretch
+ * out the selected period x axis, in case it is shorter than one of the compared periods (as in the example above).
+ */
+ private function addComparisonXLabels(array &$xLabels, DataTable $table)
+ {
+ $comparePeriods = $table->getMetadata('comparePeriods') ?: [];
+ $compareDates = $table->getMetadata('compareDates') ?: [];
+
+ // get rid of selected period
+ array_shift($comparePeriods);
+ array_shift($compareDates);
+
+ foreach (array_values($comparePeriods) as $index => $period) {
+ $date = $compareDates[$index];
+
+ $range = Factory::build($period, $date);
+ foreach ($range->getSubperiods() as $subperiod) {
+ $xLabels[$index + 1][] = $subperiod;
+ }
+ }
+ }
+
+ /**
+ * @param array $xLabels
+ * @param DataTable[] $dataTables
+ * @throws \Exception
+ */
+ private function addSelectedSeriesXLabels(array &$xLabels, array $dataTables)
+ {
+ $xTicksCount = count($dataTables);
+ foreach ($xLabels as $labelSeries) {
+ $xTicksCount = max(count($labelSeries), $xTicksCount);
+ }
+
+ /** @var Date $startDate */
+ $startDate = reset($dataTables)->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getDateStart();
+ $periodType = reset($dataTables)->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLabel();
+
+ for ($i = 0; $i < $xTicksCount; ++$i) {
+ $period = Factory::build($periodType, $startDate->addPeriod($i, $periodType));
+ $xLabels[0][] = $period;
+ }
+ }
+
+ private function setNonComparisonSeriesData(array &$allSeriesData, $rowLabel, $columnName, DataTable\Map $dataTable)
+ {
+ $seriesLabel = $this->getSeriesLabel($rowLabel, $columnName);
+
+ $seriesData = $this->getSeriesData($rowLabel, $columnName, $dataTable);
+ $allSeriesData[$seriesLabel] = $seriesData;
+ }
+
+ private function setComparisonSeriesData(array &$allSeriesData, array $seriesLabels, $rowLabel, $columnName, DataTable\Map $dataTable)
+ {
+ foreach ($dataTable->getDataTables() as $label => $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);
+ }
+
+ if (empty($row)
+ || empty($row->getComparisons())
+ ) {
+ foreach ($seriesLabels as $seriesIndex => $seriesLabelPrefix) {
+ $wholeSeriesLabel = $this->getComparisonSeriesLabelFromCompareSeries($seriesLabelPrefix, $columnName, $rowLabel);
+ $allSeriesData[$wholeSeriesLabel][] = 0;
+ }
+
+ continue;
+ }
+
+ /** @var DataTable $comparisonTable */
+ $comparisonTable = $row->getComparisons();
+ foreach ($comparisonTable->getRows() as $compareRow) {
+ $seriesLabel = $this->getComparisonSeriesLabel($compareRow, $columnName, $rowLabel);
+ $allSeriesData[$seriesLabel][] = $compareRow->getColumn($columnName);
+ }
+
+ $totalsRow = $comparisonTable->getTotalsRow();
+ if ($totalsRow) {
+ $seriesLabel = $this->getComparisonSeriesLabel($totalsRow, $columnName, $rowLabel);
+ $allSeriesData[$seriesLabel][] = $totalsRow->getColumn($columnName);
+ }
+ }
+ }
+
+ private function getSeriesMetadata(array $rowsToDisplay, array $columnsToDisplay, array $units, array $dataTables)
+ {
+ $seriesMetadata = null; // maps series labels to any metadata of the series
+ $seriesUnits = array(); // maps series labels to unit labels
+ $seriesToXAxis = []; // maps series index to x-axis index (groups of metrics for a single comparison will use the same x-axis)
+
+ $table = reset($dataTables);
+ $seriesLabels = $table->getMetadata('comparisonSeries') ?: [];
+ foreach ($rowsToDisplay as $rowIndex => $rowLabel) {
+ foreach ($columnsToDisplay as $columnIndex => $columnName) {
+ if ($this->isComparing) {
+ foreach ($seriesLabels as $seriesIndex => $seriesLabel) {
+ $wholeSeriesLabel = $this->getComparisonSeriesLabelFromCompareSeries($seriesLabel, $columnName, $rowLabel);
+
+ $allSeriesData[$wholeSeriesLabel] = [];
+
+ $metricIndex = $rowIndex * count($columnsToDisplay) + $columnIndex;
+ $seriesMetadata[$wholeSeriesLabel] = [
+ 'metricIndex' => $metricIndex,
+ 'seriesIndex' => $seriesIndex,
+ ];
+
+ $seriesUnits[$wholeSeriesLabel] = $units[$columnName];
+
+ list($periodIndex, $segmentIndex) = DataComparisonFilter::getIndividualComparisonRowIndices($table, $seriesIndex);
+ $seriesToXAxis[] = $periodIndex;
+ }
+ } else {
+ $seriesLabel = $this->getSeriesLabel($rowLabel, $columnName);
+ $seriesUnits[$seriesLabel] = $units[$columnName];
+ }
+ }
+ }
+
+ return [$seriesMetadata, $seriesUnits, $seriesLabels, $seriesToXAxis];
+ }
}
diff --git a/plugins/CoreVisualizations/Visualizations/Graph.php b/plugins/CoreVisualizations/Visualizations/Graph.php
index f7767d9527..bc37ea8747 100644
--- a/plugins/CoreVisualizations/Visualizations/Graph.php
+++ b/plugins/CoreVisualizations/Visualizations/Graph.php
@@ -63,6 +63,14 @@ abstract class Graph extends Visualization
$this->requestConfig->request_parameters_to_modify['format_metrics'] = 1;
+ // if addTotalRow was called in GenerateGraphHTML, add a row containing totals of
+ // different metrics
+ if ($this->config->add_total_row) {
+ $this->requestConfig->request_parameters_to_modify['totals'] = 1;
+ $this->requestConfig->request_parameters_to_modify['keep_totals_row'] = 1;
+ $this->requestConfig->request_parameters_to_modify['keep_totals_row_label'] = Piwik::translate('General_Total');
+ }
+
$this->metricsFormatter = new Numeric();
}
diff --git a/plugins/CoreVisualizations/Visualizations/HtmlTable.php b/plugins/CoreVisualizations/Visualizations/HtmlTable.php
index 4bf717fd1b..a07cc6af2b 100644
--- a/plugins/CoreVisualizations/Visualizations/HtmlTable.php
+++ b/plugins/CoreVisualizations/Visualizations/HtmlTable.php
@@ -42,6 +42,15 @@ class HtmlTable extends Visualization
public function beforeLoadDataTable()
{
$this->checkRequestIsNotForMultiplePeriods();
+
+ if ($this->isComparing()) {
+ // forward the comparisonIdSubtables var if present so it will be used when next/prev links are clicked
+ $comparisonIdSubtables = Common::getRequestVar('comparisonIdSubtables', false, 'string');
+ if (!empty($comparisonIdSubtables)) {
+ $comparisonIdSubtables = Common::unsanitizeInputValue($comparisonIdSubtables);
+ $this->config->custom_parameters['comparisonIdSubtables'] = $comparisonIdSubtables;
+ }
+ }
}
public function beforeRender()
@@ -96,6 +105,12 @@ class HtmlTable extends Visualization
$this->config->columns_to_display = $this->dataTable->getColumns();
}
+ if ($this->isComparing()
+ && !empty($this->dataTable)
+ ) {
+ $this->assignTemplateVar('comparisonTotals', $this->dataTable->getMetadata('comparisonTotals'));
+ }
+
// Note: This needs to be done right before rendering, as otherwise some plugins might change the columns to display again
if ($this->isFlattened()) {
$dimensions = $this->dataTable->getMetadata('dimensions');
@@ -144,6 +159,11 @@ class HtmlTable extends Visualization
});
}
}
+
+ $this->assignTemplateVar('segmentTitlePretty', $this->dataTable->getMetadata('segmentPretty'));
+
+ $period = $this->dataTable->getMetadata('period');
+ $this->assignTemplateVar('periodTitlePretty', $period ? $period->getLocalizedShortString() : '');
}
public function beforeGenericFiltersAreAppliedToLoadedDataTable()
@@ -212,6 +232,11 @@ class HtmlTable extends Visualization
return null;
}
+ public function supportsComparison()
+ {
+ return true;
+ }
+
protected function isFlattened()
{
return $this->requestConfig->flat || Common::getRequestVar('flat', '');
diff --git a/plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php b/plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php
index 579016f122..0da05ea68e 100644
--- a/plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php
+++ b/plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php
@@ -50,6 +50,11 @@ class Bar extends JqplotGraph
protected function makeDataGenerator($properties)
{
- return JqplotDataGenerator::factory('bar', $properties);
+ return JqplotDataGenerator::factory('bar', $properties, $this);
+ }
+
+ public function supportsComparison()
+ {
+ return true;
}
}
diff --git a/plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php b/plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php
index 5de7fc5114..337bb233b2 100644
--- a/plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php
+++ b/plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php
@@ -9,8 +9,11 @@
namespace Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
+use Piwik\API\Request as ApiRequest;
use Piwik\Common;
use Piwik\DataTable;
+use Piwik\Period;
+use Piwik\Period\Factory;
use Piwik\Period\Range;
use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator;
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph;
@@ -43,7 +46,9 @@ class Evolution extends JqplotGraph
public function beforeLoadDataTable()
{
- $this->calculateEvolutionDateRange();
+ if (!$this->isComparing()) {
+ $this->calculateEvolutionDateRange();
+ }
parent::beforeLoadDataTable();
@@ -55,6 +60,33 @@ class Evolution extends JqplotGraph
}
$this->config->custom_parameters['columns'] = $this->config->columns_to_display;
+
+ if ($this->isComparing()) {
+ $requestArray = $this->request->getRequestArray();
+ $requestArray = ApiRequest::getRequestArrayFromString($requestArray);
+
+ $requestingPeriod = Factory::build($requestArray['period'], $requestArray['date']);
+
+ $this->requestConfig->request_parameters_to_modify['period'] = 'day';
+ $this->requestConfig->request_parameters_to_modify['date'] = $requestingPeriod->getDateStart()->toString() . ',' . $requestingPeriod->getDateEnd()->toString();
+
+ if (!empty($requestArray['comparePeriods'])) {
+ foreach ($requestArray['comparePeriods'] as $index => $comparePeriod) {
+ $compareDate = $requestArray['compareDates'][$index];
+ if (Period::isMultiplePeriod($compareDate, $comparePeriod)) {
+ continue;
+ }
+
+ $comparePeriodObj = Factory::build($comparePeriod, $compareDate);
+
+ $requestArray['comparePeriods'][$index] = 'day';
+ $requestArray['compareDates'][$index] = $comparePeriodObj->getRangeString();
+ }
+
+ $this->requestConfig->request_parameters_to_modify['compareDates'] = $requestArray['compareDates'];
+ $this->requestConfig->request_parameters_to_modify['comparePeriods'] = $requestArray['comparePeriods'];
+ }
+ }
}
public function afterAllFiltersAreApplied()
@@ -70,7 +102,7 @@ class Evolution extends JqplotGraph
protected function makeDataGenerator($properties)
{
- return JqplotDataGenerator::factory('evolution', $properties);
+ return JqplotDataGenerator::factory('evolution', $properties, $this);
}
/**
@@ -199,4 +231,9 @@ class Evolution extends JqplotGraph
return ceil($paddedCount / $steps);
}
+
+ public function supportsComparison()
+ {
+ return true;
+ }
}
diff --git a/plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php b/plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php
index 3fafdd5e7b..045047049f 100644
--- a/plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php
+++ b/plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php
@@ -55,6 +55,6 @@ class Pie extends JqplotGraph
protected function makeDataGenerator($properties)
{
- return JqplotDataGenerator::factory('pie', $properties);
+ return JqplotDataGenerator::factory('pie', $properties, $this);
}
}
diff --git a/plugins/CoreVisualizations/Visualizations/Sparkline.php b/plugins/CoreVisualizations/Visualizations/Sparkline.php
index a2bdb09d2c..b59a89ba5d 100644
--- a/plugins/CoreVisualizations/Visualizations/Sparkline.php
+++ b/plugins/CoreVisualizations/Visualizations/Sparkline.php
@@ -11,7 +11,9 @@ namespace Piwik\Plugins\CoreVisualizations\Visualizations;
use Exception;
use Piwik\Common;
use Piwik\DataTable;
+use Piwik\Period;
use Piwik\Plugin\ViewDataTable;
+use Piwik\Site;
/**
* Reads the requested DataTable from the API and prepare data for the Sparkline view.
@@ -21,6 +23,11 @@ class Sparkline extends ViewDataTable
{
const ID = 'sparkline';
+ public function supportsComparison()
+ {
+ return true;
+ }
+
/**
* @see ViewDataTable::main()
* @return mixed
@@ -29,22 +36,61 @@ class Sparkline extends ViewDataTable
{
// If period=range, we force the sparkline to draw daily data points
$period = Common::getRequestVar('period');
- if ($period == 'range') {
+ $date = Common::getRequestVar('date');
+
+ if ($period == 'range'
+ || $this->isComparing()
+ ) {
+ $periodObj = Period\Factory::build($period, $date);
$_GET['period'] = 'day';
+ $_GET['date'] = $periodObj->getRangeString();
+ }
+
+ if ($this->isComparing()) {
+ $this->transformSingleComparisonPeriods();
}
$this->loadDataTableFromAPI();
// then revert the hack for potentially subsequent getRequestVar
$_GET['period'] = $period;
+ $_GET['date'] = $date;
- $values = $this->getValuesFromDataTable($this->dataTable);
- if (empty($values)) {
- $values = array_fill(0, 30, 0);
- }
+ $columnToPlot = $this->getColumnToPlot();
$graph = new \Piwik\Visualization\Sparkline();
- $graph->setValues($values);
+
+ if ($this->isComparing()) {
+ $otherSeries = [];
+
+ $comparisonSeries = $this->getComparisonSeries($this->dataTable);
+ foreach ($comparisonSeries as $seriesName) {
+ $otherSeries[$seriesName] = [];
+ }
+
+ $this->dataTable->filter(function (DataTable $table) use ($comparisonSeries, &$otherSeries, $columnToPlot) {
+ foreach ($table->getRows() as $row) {
+ $comparisons = $row->getComparisons();
+ if (empty($comparisons)) {
+ continue;
+ }
+
+ foreach ($comparisons->getRows() as $comparisonRow) {
+ $compareSeriesPretty = $comparisonRow->getMetadata('compareSeriesPretty');
+ $otherSeries[$compareSeriesPretty][] = $comparisonRow->getColumn($columnToPlot);
+ }
+ }
+ });
+
+ foreach ($otherSeries as $seriesValues) {
+ $seriesValues = $this->ensureValuesEvenIfEmpty($seriesValues);
+ $graph->addSeries($seriesValues);
+ }
+ } else {
+ $values = $this->getValuesFromDataTable($this->dataTable, $columnToPlot);
+ $values = $this->ensureValuesEvenIfEmpty($values);
+ $graph->addSeries($values);
+ }
$height = Common::getRequestVar('height', 0, 'int');
if (!empty($height)) {
@@ -100,7 +146,7 @@ class Sparkline extends ViewDataTable
return $values;
}
- protected function getValuesFromDataTable($dataTable)
+ private function getColumnToPlot()
{
$columns = $this->config->columns_to_display;
@@ -113,6 +159,11 @@ class Sparkline extends ViewDataTable
}
}
+ return $columnToPlot;
+ }
+
+ protected function getValuesFromDataTable($dataTable, $columnToPlot)
+ {
// 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) {
@@ -125,4 +176,41 @@ class Sparkline extends ViewDataTable
return $values;
}
+
+ private function ensureValuesEvenIfEmpty(array $values)
+ {
+ if (empty($values)) {
+ return array_fill(0, 30, 0);
+ }
+ return $values;
+ }
+
+ private function getComparisonSeries(DataTable\DataTableInterface $dataTable)
+ {
+ if ($dataTable instanceof DataTable\Map) {
+ return reset($dataTable->getDataTables())->getMetadata('comparisonSeries') ?: [];
+ } else {
+ return $dataTable->getMetadata('comparisonSeries') ?: [];
+ }
+ }
+
+ private function transformSingleComparisonPeriods()
+ {
+ $comparePeriods = Common::getRequestVar('comparePeriods', $default = [], $type = 'array');
+ $compareDates = Common::getRequestVar('compareDates', $default = [], $type = 'array');
+
+ foreach ($comparePeriods as $index => $comparePeriod) {
+ $compareDate = $compareDates[$index];
+ if (Period::isMultiplePeriod($compareDate, $comparePeriod)) {
+ continue;
+ }
+
+ $periodObj = Period\Factory::build($comparePeriod, $compareDate);
+ $comparePeriods[$index] = 'day';
+ $compareDates[$index] = $periodObj->getRangeString();
+ }
+
+ $this->requestConfig->request_parameters_to_modify['comparePeriods'] = $comparePeriods;
+ $this->requestConfig->request_parameters_to_modify['compareDates'] = $compareDates;
+ }
}
diff --git a/plugins/CoreVisualizations/Visualizations/Sparklines.php b/plugins/CoreVisualizations/Visualizations/Sparklines.php
index 2d11723bef..c3d8556e00 100644
--- a/plugins/CoreVisualizations/Visualizations/Sparklines.php
+++ b/plugins/CoreVisualizations/Visualizations/Sparklines.php
@@ -8,10 +8,14 @@
*/
namespace Piwik\Plugins\CoreVisualizations\Visualizations;
+use Piwik\API\Request;
use Piwik\Common;
use Piwik\DataTable;
use Piwik\Metrics;
+use Piwik\Period\Factory;
use Piwik\Plugin\ViewDataTable;
+use Piwik\Plugins\API\Filter\DataComparisonFilter;
+use Piwik\SettingsPiwik;
use Piwik\Url;
use Piwik\View;
@@ -48,6 +52,11 @@ class Sparklines extends ViewDataTable
return new Sparklines\Config();
}
+ public function supportsComparison()
+ {
+ return true;
+ }
+
/**
* @see ViewDataTable::main()
* @return mixed
@@ -82,6 +91,7 @@ class Sparklines extends ViewDataTable
$view->titleAttributes = $this->config->title_attributes;
$view->footerMessage = $this->config->show_footer_message;
$view->areSparklinesLinkable = $this->config->areSparklinesLinkable();
+ $view->isComparing = $this->isComparing();
$view->title = '';
if ($this->config->show_title) {
@@ -103,13 +113,35 @@ class Sparklines extends ViewDataTable
}
}
- $translations = $this->config->translations;
-
$firstRow = $data->getFirstRow();
+ $comparisons = $firstRow->getComparisons();
+
+ $originalDate = Common::getRequestVar('date');
+ $originalPeriod = Common::getRequestVar('period');
+
+ if ($this->isComparing() && !empty($comparisons)) {
+ $comparisonRows = [];
+ foreach ($comparisons->getRows() as $comparisonRow) {
+ $segment = $comparisonRow->getMetadata('compareSegment');
+ if ($segment === false) {
+ $segment = Request::getRawSegmentFromRequest() ?: '';;
+ }
- foreach ($this->config->getSparklineMetrics() as $sparklineMetric) {
+ $date = $comparisonRow->getMetadata('compareDate');
+ $period = $comparisonRow->getMetadata('comparePeriod');
+
+ $comparisonRows[$segment][$period][$date] = $comparisonRow;
+ }
+ }
+
+ foreach ($this->config->getSparklineMetrics() as $sparklineMetricIndex => $sparklineMetric) {
$column = $sparklineMetric['columns'];
$order = $sparklineMetric['order'];
+ $graphParams = $sparklineMetric['graphParams'];
+
+ if (!isset($order)) {
+ $order = 1000;
+ }
if ($column === 'label') {
continue;
@@ -120,31 +152,87 @@ class Sparklines extends ViewDataTable
continue;
}
- if (!is_array($column)) {
- $column = array($column);
- }
+ $sparklineUrlParams = array(
+ 'columns' => $column,
+ 'module' => $this->requestConfig->getApiModuleToRequest(),
+ 'action' => $this->requestConfig->getApiMethodToRequest()
+ );
+
+ if ($this->isComparing() && !empty($comparisons)) {
+ $periodObj = Factory::build($originalPeriod, $originalDate);
+
+ $sparklineUrlParams['compareSegments'] = [];
+
+ $comparePeriods = $data->getMetadata('comparePeriods');
+ $compareDates = $data->getMetadata('compareDates');
+
+ $compareSegments = $data->getMetadata('compareSegments');
+ foreach ($compareSegments as $segmentIndex => $segment) {
+ $metrics = [];
+ $seriesIndices = [];
- $values = array();
- $descriptions = array();
+ foreach ($comparePeriods as $periodIndex => $period) {
+ $date = $compareDates[$periodIndex];
- foreach ($column as $col) {
- $value = $firstRow->getColumn($col);
+ $compareRow = $comparisonRows[$segment][$period][$date];
+ $segmentPretty = $compareRow->getMetadata('compareSegmentPretty');
+ $periodPretty = $compareRow->getMetadata('comparePeriodPretty');
- if ($value === false) {
- $value = 0;
+ $columnToUse = $this->removeUniqueVisitorsIfNotEnabledForPeriod($column, $period);
+ list($compareValues, $compareDescriptions, $evolutions) = $this->getValuesAndDescriptions($compareRow, $columnToUse);
+
+ foreach ($compareValues as $i => $value) {
+ $metricInfo = [
+ 'value' => $value,
+ 'description' => $compareDescriptions[$i],
+ 'group' => $periodPretty,
+ ];
+
+ if ($periodIndex > 0
+ && isset($evolutions[$i])
+ ) {
+ $metricInfo['evolution'] = $evolutions[$i];
+ }
+
+ $metrics[] = $metricInfo;
+ }
+
+ $seriesIndices[] = DataComparisonFilter::getComparisonSeriesIndex($data, $periodIndex, $segmentIndex);
+ }
+
+ // only set the title (which is the segment) if comparing more than one segment
+ $title = count($compareSegments) > 1 ? $segmentPretty : null;
+
+ $params = array_merge($sparklineUrlParams, [
+ 'segment' => $segment,
+ 'period' => $periodObj->getLabel(),
+ 'date' => $periodObj->getRangeString(),
+ ]);
+ $this->config->addSparkline($params, $metrics, $desc = null, null, ($order * 100) + $segmentIndex, $title, $sparklineMetricIndex, $seriesIndices, $graphParams);
}
+ } else {
+ list($values, $descriptions) = $this->getValuesAndDescriptions($firstRow, $column);
- $values[] = $value;
- $descriptions[] = isset($translations[$col]) ? $translations[$col] : $col;
- }
+ $metrics = [];
+ foreach ($values as $i => $value) {
+ $newMetric = [
+ 'value' => $value,
+ 'description' => $descriptions[$i],
+ ];
- $sparklineUrlParams = array(
- 'columns' => $column,
- 'module' => $this->requestConfig->getApiModuleToRequest(),
- 'action' => $this->requestConfig->getApiMethodToRequest()
- );
+ $metrics[] = $newMetric;
+ }
- $this->config->addSparkline($sparklineUrlParams, $values, $descriptions, null, $order);
+ $evolution = null;
+
+ $computeEvolution = $this->config->compute_evolution;
+ if ($computeEvolution) {
+ $evolution = $computeEvolution(array_combine($column, $values));
+ $newMetric['evolution'] = $evolution;
+ }
+
+ $this->config->addSparkline($sparklineUrlParams, $metrics, $desc = null, $evolution, $order, $title = null, $group = $sparklineMetricIndex, $seriesIndices = null, $graphParams);
+ }
}
}
@@ -161,4 +249,44 @@ class Sparklines extends ViewDataTable
$table->applyQueuedFilters();
}
+
+ private function getValuesAndDescriptions(DataTable\Row $firstRow, $columns)
+ {
+ if (!is_array($columns)) {
+ $columns = array($columns);
+ }
+
+ $translations = $this->config->translations;
+
+ $values = array();
+ $descriptions = array();
+ $evolutions = [];
+
+ foreach ($columns as $col) {
+ $value = $firstRow->getColumn($col);
+
+ if ($value === false) {
+ $value = 0;
+ }
+
+ $evolution = $firstRow->getColumn($col . '_change'); // for comparison rows
+ if ($evolution !== false) {
+ $evolutions[] = ['percent' => ltrim($evolution, '+'), 'tooltip' => ''];
+ }
+
+ $values[] = $value;
+ $descriptions[] = isset($translations[$col]) ? $translations[$col] : $col;
+ }
+
+ return [$values, $descriptions, $evolutions];
+ }
+
+ private function removeUniqueVisitorsIfNotEnabledForPeriod($columns, $period)
+ {
+ if (SettingsPiwik::isUniqueVisitorsEnabled($period)) {
+ return $columns;
+ }
+
+ return array_diff($columns, ['nb_users', 'nb_uniq_visitors']);
+ }
}
diff --git a/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php b/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php
index 86944ccc5a..e5fe3da1c0 100644
--- a/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php
+++ b/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php
@@ -12,6 +12,7 @@ use Piwik\Common;
use Piwik\DataTable\Filter\CalculateEvolutionFilter;
use Piwik\Metrics;
use Piwik\NoAccessException;
+use Piwik\Period\Factory;
use Piwik\Period\Range;
use Piwik\Site;
use Piwik\Url;
@@ -46,6 +47,15 @@ class Config extends \Piwik\ViewDataTable\Config
*/
public $title_attributes = array();
+ /**
+ * If supplied, this function is used to compute the evolution percent displayed next to non-comparison sparkline views.
+ *
+ * The function is passed an array mapping column names with column values.
+ *
+ * @var callable
+ */
+ public $compute_evolution = null;
+
public function __construct()
{
parent::__construct();
@@ -145,12 +155,15 @@ class Config extends \Piwik\ViewDataTable\Config
* @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.
+ * @param array $graphParams The params to use when changing an associated evolution graph. By default this is determined
+ * from the sparkline URL, but sometimes the sparkline API method may not match the evolution graph API method.
*/
- public function addSparklineMetric($metricName, $order = null)
+ public function addSparklineMetric($metricName, $order = null, $graphParams = null)
{
$this->sparkline_metrics[] = array(
'columns' => $metricName,
- 'order' => $order
+ 'order' => $order,
+ 'graphParams' => $graphParams,
);
}
@@ -167,7 +180,10 @@ class Config extends \Piwik\ViewDataTable\Config
$this->sparklines[] = array(
'url' => '',
'metrics' => array(),
- 'order' => $this->getSparklineOrder($order)
+ 'order' => $this->getSparklineOrder($order),
+
+ // adding this group ensures the sparkline will be placed between individual sparklines, and not in their own group together
+ 'group' => 'placeholder' . count($this->sparklines),
);
}
@@ -194,62 +210,87 @@ class Config extends \Piwik\ViewDataTable\Config
* '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.
+ * @param string $title The title of this specific sparkline. It is displayed on the left above the sparkline image.
+ * @param string $group The ID of the group for this sparkline.
+ * @param int $seriesIndices The indexes of each series displayed in the sparkline. This determines what color is used for each series. Mainly used for comparison.
+ * @param array $graphParams The params to use when changing an associated evolution graph. By default this is determined
+ * from the sparkline URL, but sometimes the sparkline API method may not match the evolution graph API method.
* @throws \Exception In case an evolution parameter is set but has wrong data structure
*/
- public function addSparkline($requestParamsForSparkline, $value, $description, $evolution = null, $order = null)
+ public function addSparkline($requestParamsForSparkline, $metricInfos, $description, $evolution = null, $order = null, $title = null, $group = '', $seriesIndices = null, $graphParams = null)
{
$metrics = array();
- if (is_array($value)) {
- $values = $value;
+ if ($description === null && is_array($metricInfos)) {
+ $metrics = $metricInfos;
} else {
- $values = array($value);
- }
+ $value = $metricInfos;
- if (!is_array($description)) {
- $description = array($description);
+ if (is_array($value)) {
+ $values = $value;
+ } else {
+ $values = array($value);
+ }
+
+ if (!is_array($description)) {
+ $description = array($description);
+ }
+
+ if (count($values) === count($description)) {
+ foreach ($values as $index => $value) {
+ $metrics[] = array(
+ '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($requestParamsForSparkline['columns'])
&& is_array($requestParamsForSparkline['columns'])
- && count($requestParamsForSparkline['columns']) === count($values)) {
+ && count($requestParamsForSparkline['columns']) === count($metrics)) {
$columns = array_values($requestParamsForSparkline['columns']);
} elseif (!empty($requestParamsForSparkline['columns'])
&& is_string($requestParamsForSparkline['columns'])
- && count($values) === 1) {
+ && count($metrics) === 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);
+ foreach ($metrics as $index => $metricInfo) {
+ $metrics[$index]['column'] = isset($columns[$index]) ? $columns[$index] : '';
}
if (empty($metrics)) {
return;
}
+ $groupedMetrics = [];
+ foreach ($metrics as $metricInfo) {
+ $metricGroup = isset($metricInfo['group']) ? $metricInfo['group'] : '';
+ $groupedMetrics[$metricGroup][] = $metricInfo;
+ }
+
$sparkline = array(
'url' => $this->getUrlSparkline($requestParamsForSparkline),
- 'metrics' => $metrics,
- 'order' => $this->getSparklineOrder($order)
+ 'metrics' => $groupedMetrics,
+ 'order' => $this->getSparklineOrder($order),
+ 'title' => $title,
+ 'group' => $group,
+ 'seriesIndices' => $seriesIndices,
+ 'graphParams' => $graphParams,
);
if (!empty($evolution)) {
if (!is_array($evolution) ||
!array_key_exists('currentValue', $evolution) ||
- !array_key_exists('pastValue', $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');
}
@@ -300,7 +341,12 @@ class Config extends \Piwik\ViewDataTable\Config
return ($a['order'] < $b['order']) ? -1 : 1;
});
- return $this->sparklines;
+ $sparklines = [];
+ foreach ($this->sparklines as $sparkline) {
+ $group = $sparkline['group'];
+ $sparklines[$group][] = $sparkline;
+ }
+ return $sparklines;
}
private function getSparklineOrder($order)
@@ -390,7 +436,13 @@ class Config extends \Piwik\ViewDataTable\Config
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);
+ if (!isset($paramsToSet['date'])
+ || !Range::isMultiplePeriod($paramsToSet['date'], $period)
+ ) {
+ $paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $site);
+ } else {
+ $paramDate = $paramsToSet['date'];
+ }
$params = array_merge($paramsToSet, array('date' => $paramDate));
return $params;
diff --git a/plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.js b/plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.js
index 353a5faebc..c7f2c41dc6 100644
--- a/plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.js
+++ b/plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.js
@@ -164,7 +164,7 @@
function getLastPeriodDate() {
var RangePeriod = piwikPeriods.get('range');
var result = RangePeriod.getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
- return $.datepicker.formatDate('yy-mm-dd', result);
+ return piwikPeriods.format(result);
}
function setWidgetTitle() {
@@ -269,7 +269,7 @@
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]);
+ return piwikPeriods.format(dateRange[0]) + ',' + piwikPeriods.format(dateRange[1]);
}
function isIdGoalSet() {
diff --git a/plugins/CoreVisualizations/javascripts/jqplot.js b/plugins/CoreVisualizations/javascripts/jqplot.js
index a509f401a6..c53cdcb493 100644
--- a/plugins/CoreVisualizations/javascripts/jqplot.js
+++ b/plugins/CoreVisualizations/javascripts/jqplot.js
@@ -634,9 +634,7 @@ function rowEvolutionGetMetricNameFromRow(tr)
* Sets the colors used to render this graph.
*/
_setColors: function () {
- var colorManager = piwik.ColorManager,
- seriesColorNames = ['series1', 'series2', 'series3', 'series4', 'series5',
- 'series6', 'series7', 'series8', 'series9', 'series10'];
+ var colorManager = piwik.ColorManager;
var viewDataTable = $('#' + this.workingDivId).data('uiControlObject').param['viewDataTable'];
@@ -651,11 +649,31 @@ function rowEvolutionGetMetricNameFromRow(tr)
var namespace = graphType + '-graph-colors';
- this.jqplotParams.seriesColors = colorManager.getColors(namespace, seriesColorNames, true);
+ this._setSeriesColors(namespace);
+
this.jqplotParams.grid.background = colorManager.getColor(namespace, 'grid-background');
this.jqplotParams.grid.borderColor = colorManager.getColor(namespace, 'grid-border');
this.tickColor = colorManager.getColor(namespace, 'ticks');
this.singleMetricColor = colorManager.getColor(namespace, 'single-metric-label')
+ },
+
+ _setSeriesColors: function (namespace) {
+ var colorManager = piwik.ColorManager,
+ seriesColorNames = ['series0', 'series1', 'series2', 'series3', 'series4', 'series5',
+ 'series6', 'series7', 'series8', 'series9', 'series10'];
+
+ var comparisonService = piwikHelper.getAngularDependency('piwikComparisonsService');
+ if (comparisonService.isComparing() && typeof this.jqplotParams.series[0].seriesIndex !== 'undefined') {
+ namespace = 'comparison-series-color';
+
+ seriesColorNames = [];
+ this.jqplotParams.series.forEach(function (s) {
+ var seriesColorName = comparisonService.getSeriesColorName(s.seriesIndex, s.metricIndex);
+ seriesColorNames.push(seriesColorName);
+ });
+ }
+
+ this.jqplotParams.seriesColors = colorManager.getColors(namespace, seriesColorNames, true);
}
});
@@ -1040,7 +1058,9 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function () {
c.markerRenderer.init();
var position = series.gridData[tick];
- c.markerRenderer.draw(position[0], position[1], c.piwikHighlightCanvas._ctx);
+ if (typeof position !== 'undefined') {
+ c.markerRenderer.draw(position[0], position[1], c.piwikHighlightCanvas._ctx);
+ }
}
}
diff --git a/plugins/CoreVisualizations/javascripts/jqplotBarGraph.js b/plugins/CoreVisualizations/javascripts/jqplotBarGraph.js
index cd21308f73..24af4c2e9d 100644
--- a/plugins/CoreVisualizations/javascripts/jqplotBarGraph.js
+++ b/plugins/CoreVisualizations/javascripts/jqplotBarGraph.js
@@ -22,6 +22,9 @@
_setJqplotParameters: function (params) {
JqplotGraphDataTable.prototype._setJqplotParameters.call(this, params);
+ var barMargin = this.data[0].length > 10 ? 2 : 10;
+ var minBarWidth = 10;
+
this.jqplotParams.seriesDefaults = {
renderer: $.jqplot.BarRenderer,
rendererOptions: {
@@ -29,7 +32,7 @@
shadowDepth: 2,
shadowAlpha: .2,
fillToZero: true,
- barMargin: this.data[0].length > 10 ? 2 : 10
+ barMargin: barMargin
}
};
@@ -48,6 +51,18 @@
this.jqplotParams.canvasLegend = {
show: true
};
+
+ var comparisonService = piwikHelper.getAngularDependency('piwikComparisonsService');
+ if (comparisonService.isComparing()) {
+ var seriesCount = this.jqplotParams.series.length;
+ var dataCount = this.data[0].length;
+
+ var totalBars = seriesCount * dataCount;
+ var totalMinWidth = (minBarWidth + barMargin) * totalBars + 50;
+
+ this.$element.find('.piwik-graph').css('min-width', totalMinWidth + 'px');
+ this.$element.css('overflow-x', 'scroll');
+ }
},
_bindEvents: function () {
diff --git a/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js b/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js
index b77add4962..012e5e8bc1 100644
--- a/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js
+++ b/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js
@@ -96,19 +96,53 @@
.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 dataByAxis = {};
+ for (var d = 0; d < self.data.length; ++d) {
+ var valueUnformatted = self.data[d][tick];
+ if (typeof valueUnformatted === 'undefined' || valueUnformatted === null) {
+ continue;
+ }
+
+ var axis = self.jqplotParams.series[d]._xaxis || 'xaxis';
+ if (!dataByAxis[axis]) {
+ dataByAxis[axis] = [];
+ }
+
+ var value = self.formatY(valueUnformatted, d);
var series = self.jqplotParams.series[d].label;
- text.push('<strong>' + value + '</strong> ' + piwikHelper.htmlEntities(series));
+
+ var seriesColor = self.jqplotParams.seriesColors[d];
+
+ dataByAxis[axis].push('<span class="tooltip-series-color" style="background-color: ' + seriesColor + ';"/>' + '<strong>' + value + '</strong> ' + piwikHelper.htmlEntities(series));
+ }
+
+ var xAxisCount = 0;
+ Object.keys(self.jqplotParams.axes).forEach(function (axis) {
+ if (axis.substring(0, 1) === 'x') {
+ ++xAxisCount;
+ }
+ });
+
+ var content = '';
+ for (var i = 0; i < xAxisCount; ++i) {
+ var axisName = i === 0 ? 'xaxis' : 'x' + (i + 1) + 'axis';
+ if (!dataByAxis[axisName] || !dataByAxis[axisName].length) {
+ continue;
+ }
+
+ if (typeof self.jqplotParams.axes[axisName].labels != 'undefined') {
+ label = self.jqplotParams.axes[axisName].labels[tick];
+ } else {
+ label = self.jqplotParams.axes[axisName].ticks[tick];
+ }
+
+ if (typeof label === 'undefined') { // sanity check
+ continue;
+ }
+
+ content += '<h3 class="evolution-tooltip-header">'+piwikHelper.htmlEntities(label)+'</h3>'+dataByAxis[axisName].join('<br />');
}
- var content = '<h3>'+piwikHelper.htmlEntities(label)+'</h3>'+text.join('<br />');
$(this).tooltip({
track: true,
diff --git a/plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less b/plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less
index ff2118b952..15edf722c0 100644
--- a/plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less
+++ b/plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less
@@ -23,4 +23,18 @@
}
.widget .dataTableVizBar .jqplot-graph {
padding: 0 10px 10px 10px;
+}
+
+.tooltip-series-color {
+ display: inline-block;
+ width: 11px;
+ height: 11px;
+ border-radius: 6px;
+ margin-right: 3px;
+ position: relative;
+ top: 1px;
+}
+
+.ui-tooltip h3.evolution-tooltip-header {
+ margin-top: 4px;
} \ No newline at end of file
diff --git a/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig b/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig
index 38c2101168..a9ac3f4dae 100644
--- a/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig
+++ b/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig
@@ -19,11 +19,13 @@
</tr>
{% endif %}
{% else %}
+ {% set rowIndex = properties.filter_offset|default(0) + 1 %}
{%- 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') or row.getMetadata('is_summary') -%}
{%- set shouldHighlightRow = isSummaryRow and properties.highlight_summary_row -%}
+ {% set dimensions = dataTable.getMetadata('dimensions')|default([]) %}
{# display this row if it doesn't have a subtable or if we don't replace the row with the subtable #}
{%- set showRow = subtablesAreDisabled
@@ -36,21 +38,38 @@
{% 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 %}"
+ class="{{ row.getMetadata('css_class') }} {% if rowHasSubtable %}subDataTable{% endif %}{% if shouldHighlightRow %} highlight{% endif %}{% if isSummaryRow %} summaryRow{% endif %} {% if isComparing %}parentComparisonRow{% 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 %}>
+ <td class="{% if column =='label' or column in dimensions %}label{% else %}column{% endif %} {{ cellAttributes.class|default|e('html_attr') }}"
+ {% if cellAttributes is not empty %}{% for name, value in cellAttributes %}{{ name|e('html') }}="{{ value|e('html_attr') }}" {% endfor %}{% endif %}
+ >
+ {% if isComparing and column == 'label' %}
+ <span class="prefix-numeral">{{ rowIndex }}.</span>
+ {% endif %}
+
{% include "@CoreHome/_dataTableCell.twig" with properties %}
</td>
{% endfor %}
</tr>
+
+ {% if row.getComparisons() %}
+ {% include "@CoreVisualizations/_dataTableViz_htmlTable_comparisons.twig" with {
+ 'comparedRow': row,
+ 'dataTable': row.getComparisons(),
+ 'rootDataTable': dataTable,
+ 'dimensions': dimensions,
+ } %}
+ {% endif %}
{% 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 %}
+
+ {% set rowIndex = rowIndex + 1 %}
{%- endfor -%}
{% if dataTable.getTotalsRow and properties.show_totals_row %}
{% set row = dataTable.getTotalsRow %}
@@ -58,7 +77,7 @@
<tr class="{{ row.getMetadata('css_class') }} totalsRow"
title="Total values for this table">
{% for column in properties.columns_to_display %}
- <td>
+ <td class="{% if column =='label' %}label{% else %}column{% endif %}">
{% include "@CoreHome/_dataTableCell.twig" with properties %}
</td>
{% endfor %}
diff --git a/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_comparisons.twig b/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_comparisons.twig
new file mode 100644
index 0000000000..fec4652ed4
--- /dev/null
+++ b/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_comparisons.twig
@@ -0,0 +1,83 @@
+{% if properties.columns_to_display is not empty %}
+{% set lastSeenComparePeriod = false %}
+{% set comparedPeriodPretty = dataTable.getFirstRow().getMetadata('comparePeriodPretty') %}
+{% set isComparingSegments = rootDataTable.getMetadata('compareSegments')|default([])|length > 1 %}
+{% set isComparingPeriods = rootDataTable.getMetadata('comparePeriods')|default([])|length > 1 %}
+{%- for rowId, row in dataTable.getRows() -%}
+ {% set comparePeriod = row.getMetadata('comparePeriodPretty') %}
+ {% if lastSeenComparePeriod != comparePeriod and isComparingSegments and isComparingPeriods %}
+ <tr class="comparePeriod">
+ <td class="label">
+ {{ comparePeriod }}
+ </td>
+
+ {# add extra empty columns for sticky column scrolling to work #}
+ {% for column in properties.columns_to_display %}
+ {% if column != 'label' %}
+ <td class="column">&nbsp;</td>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ {% endif %}
+ {% set overrideParams = {} %}
+
+ {% if row.getMetadata('compareSegment')|default is not empty %}{% set overrideParams = overrideParams|merge({ segment: row.getMetadata('compareSegment'), compareSegments: '' }) %}{% endif %}
+ {% if row.getMetadata('comparePeriod')|default is not empty %}{% set overrideParams = overrideParams|merge({ period: row.getMetadata('comparePeriod'), comparePeriods: '' }) %}{% endif %}
+ {% if row.getMetadata('compareDate')|default is not empty %}{% set overrideParams = overrideParams|merge({ date: row.getMetadata('compareDate'), compareDates: '' }) %}{% endif %}
+ {% set idSubtable = row.getMetadata('idsubdatatable') %}
+ {% set seriesIndex = loop.index0 %}
+ <tr
+ {% if idSubtable != false %}data-idsubtable="{{ idSubtable|e('html_attr') }}"{% endif %}
+ data-comparison-series="{{ seriesIndex }}"
+ class="comparisonRow"
+ data-param-override="{{ overrideParams|json_encode|e('html_attr') }}" data-label="{{ comparedRow.getColumn('label')|e('html_attr') }}"
+ {% if row.getMetadata('segment') is not false %}data-segment-filter="{{ row.getMetadata('segment')|e('html_attr') }}"{% endif %}
+ >
+ <td class="label">
+ {% if isComparingSegments %}
+ {%- set comparisonLabel = row.getMetadata('compareSegmentPretty') -%}
+ {% else %}
+ {%- set comparisonLabel = comparePeriod -%}
+ {% endif %}
+ <span class="label">{{ comparisonLabel }}</span>
+ </td>
+ {% for dimension in dimensions %}
+ {% if loop.index0 != 0 %}
+ <td class="label">
+ &nbsp;
+ </td>
+ {% endif %}
+ {% endfor %}
+ {% for column in properties.columns_to_display %}
+ {% if column != 'label' and column not in dimensions %}
+ {% set columnValue = row.getColumn(column) %}
+ <td class="column">
+ {% set rowComparisonTotals = comparisonTotals[seriesIndex].totals|default(null) %}
+
+ {% set comparisonTooltipSuffix = '' %}
+ {% set columnChange = false %}
+ {% if row.getColumn(column ~ '_change')|default is not empty %}
+ {% set columnChange = row.getColumn(column ~ '_change')|default('+0%') %}
+ {% set comparisonTooltipSuffix = 'General_ComparisonRatioTooltip'|translate(columnChange, row.getMetadata('compareSegmentPretty'), comparedPeriodPretty) %}
+ {% endif %}
+
+ {% include "@CoreVisualizations/_dataTableViz_htmlTable_ratio.twig" with {
+ 'changePercentage': columnChange,
+ 'totals': rowComparisonTotals,
+ 'label': comparedRow.getColumn('label'),
+ 'labelColumn': properties.columns_to_display|first,
+ 'changePercantage': columnChange,
+ 'forceZero': true,
+ 'tooltipSuffix': comparisonTooltipSuffix,
+ 'translations': properties.translations,
+ 'segmentTitlePretty': row.getMetadata('compareSegmentPretty'),
+ 'periodTitlePretty': row.getMetadata('comparePeriodPretty')
+ } %}
+ <span class="value">{{ columnValue|default(0)|number(2,0)|rawSafeDecoded }}</span>
+ </td>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ {% set lastSeenComparePeriod = comparePeriod %}
+{%- endfor -%}
+{% endif %} \ No newline at end of file
diff --git a/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_ratio.twig b/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_ratio.twig
new file mode 100644
index 0000000000..8d14bb126d
--- /dev/null
+++ b/plugins/CoreVisualizations/templates/_dataTableViz_htmlTable_ratio.twig
@@ -0,0 +1,26 @@
+{% if column in properties.report_ratio_columns and (column in totals|keys or forceZero|default) -%}
+ {% set reportTotal = totals[column]|default(0) %}
+ {% if siteTotalRow|default is not empty %}
+ {% set siteTotal = siteTotalRow.getColumn(column) %}
+ {% elseif siteSummary is defined and siteSummary is not empty and siteSummary.getFirstRow %}
+ {% set siteTotal = siteSummary.getFirstRow.getColumn(column) %}
+ {% else %}
+ {% set siteTotal = 0 %}
+ {% endif %}
+
+ {% set rowPercentage = row.getColumn(column)|percentage(reportTotal, 1) %}
+ {% set metricTitle = translations[column]|default(column) %}
+
+ {% set reportRatioTooltip = 'General_ReportRatioTooltip'|translate(label, rowPercentage|e('html_attr'), reportTotal|e('html_attr'), metricTitle|e('html_attr'), '"' ~ segmentTitlePretty ~ '"', translations[labelColumn]|default(labelColumn)|e('html_attr')) %}
+
+ {% if siteTotal and siteTotal > reportTotal %}
+ {% set totalPercentage = row.getColumn(column)|percentage(siteTotal, 1) %}
+ {% set totalRatioTooltip = 'General_TotalRatioTooltip'|translate(totalPercentage, siteTotal|number(2,0), metricTitle, periodTitlePretty) %}
+ {% else %}
+ {% set totalRatioTooltip = '' %}
+ {% endif %}
+
+ <span class="ratio"
+ title="{{ reportRatioTooltip|rawSafeDecoded|raw }} {{ totalRatioTooltip|rawSafeDecoded|e('html_attr') }}{% if tooltipSuffix|default is not empty %}<br/><br/> {{ tooltipSuffix|rawSafeDecoded|e('html_attr') }}{% endif %}"
+ >&nbsp;{{ rowPercentage }} {% if changePercantage|default is not empty %}({{ changePercentage }}){% endif %}</span>
+{%- endif %}
diff --git a/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig b/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig
index 5c8390d914..e1932c523b 100644
--- a/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig
+++ b/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig
@@ -10,12 +10,23 @@
<div class="row">
<div class="col m6">
{% endif %}
-
- {% for key, sparkline in sparklines %}
- {% if key is even %}
+ {% if sparklines|length == 1 %}
+ {% for key, sparkline in sparklines|first %}
+ {% if loop.index0 is even %}
{{ macros.singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) }}
{% endif %}
{% endfor %}
+ {% else %}
+ {% for group in sparklines %}
+ {% if loop.index0 is even %}
+ <div>
+ {% for key, sparkline in group %}
+ {{ macros.singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) }}
+ {% endfor %}
+ </div>
+ {% endif %}
+ {% endfor %}
+ {% endif %}
{% if not isWidget %}
<br style="clear:left"/>
@@ -23,11 +34,23 @@
<div class="col m6">
{% endif %}
+ {% if sparklines|length == 1 %}
{% for key, sparkline in sparklines %}
- {% if key is odd %}
+ {% if loop.index0 is odd %}
{{ macros.singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) }}
{% endif %}
{% endfor %}
+ {% else %}
+ {% for group in sparklines %}
+ {% if loop.index0 is odd %}
+ <div>
+ {% for key, sparkline in group %}
+ {{ macros.singleSparkline(sparkline, allMetricsDocumentation, areSparklinesLinkable) }}
+ {% endfor %}
+ </div>
+ {% endif %}
+ {% endfor %}
+ {% endif %}
<br style="clear:left"/>
diff --git a/plugins/CoreVisualizations/templates/macros.twig b/plugins/CoreVisualizations/templates/macros.twig
index 7562b88f78..bc754f2c19 100644
--- a/plugins/CoreVisualizations/templates/macros.twig
+++ b/plugins/CoreVisualizations/templates/macros.twig
@@ -1,36 +1,52 @@
+
+{% macro sparklineEvolution(evolution) %}
+ {% set evolutionPretty = evolution.percent %}
+
+ {% if evolution.percent < 0 %}
+ {% set evolutionClass = 'negative-evolution' %}
+ {% set evolutionIcon = 'arrow_down.png' %}
+ {% elseif evolution.percent == 0 %}
+ {% set evolutionClass = 'neutral-evolution' %}
+ {% set evolutionIcon = 'stop.png' %}
+ {% else %}
+ {% set evolutionClass = 'positive-evolution' %}
+ {% set evolutionIcon = 'arrow_up.png' %}
+ {% set evolutionPretty = '+' ~ evolution.percent %}
+ {% endif %}
+
+ <span class="metricEvolution" title="{{ evolution.tooltip|rawSafeDecoded|e('html_attr') }}">
+ <img style="padding-right:4px" src="plugins/MultiSites/images/{{ evolutionIcon }}"/>
+ <strong class="{{ evolutionClass }}">{{ evolutionPretty }}</strong></span>
+{% endmacro %}
+
{% 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 class="sparkline {% if areSparklinesLinkable is defined and not areSparklinesLinkable %}notLinkable{% endif %}"
+ {% if sparkline.seriesIndices|default is not empty %}data-series-indices="{{ sparkline.seriesIndices|json_encode|e('html_attr') }}"{% endif %}
+ {% if sparkline.graphParams|default is not empty %}data-graph-params="{{ sparkline.graphParams|json_encode|e('html_attr') }}"{% endif %}
+ >
+ <div>
+ {% if sparkline.title|default is not empty %}<h6 class="sparkline-title" title="{{ sparkline.title|rawSafeDecoded|e('html_attr') }}">{{ sparkline.title }}</h6>{% endif %}
+ {% if sparkline.url %}{{ sparkline(sparkline.url)|raw }}{% endif %}
+ </div>
<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>
+ {% for groupName, group in sparkline.metrics %}
+ {% if groupName is not empty %}<span class="metric-group-title">{{ groupName }}</span>{% endif %}
+ {% for metric in group %}
+ <span class="sparkline-metrics" {% 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>
+ {% if metric.evolution is defined %}
+ {{ _self.sparklineEvolution(metric.evolution) }}
+ {% endif %}
+ {% endfor %}
{% 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>
+ {{ _self.sparklineEvolution(sparkline.evolution) }}
{% endif %}
</div>
</div>
diff --git a/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php b/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php
index 33fb6be6ab..6847ac7b2c 100644
--- a/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php
+++ b/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php
@@ -65,12 +65,18 @@ class SparklinesConfigTest extends IntegrationTestCase
$expectedSparkline = array(
'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline',
'metrics' => array (
- array ('column' => '', 'value' => 10, 'description' => 'Visits'),
+ '' => [
+ array ('value' => 10, 'description' => 'Visits', 'column' => ''),
+ ],
),
- 'order' => 999
+ 'order' => 999,
+ 'title' => null,
+ 'group' => '',
+ 'seriesIndices' => null,
+ 'graphParams' => null,
);
- $this->assertSame(array($expectedSparkline), $this->config->getSortedSparklines());
+ $this->assertSame(array($expectedSparkline), $this->config->getSortedSparklines()['']);
}
public function test_addSparkline_shouldAddAMinimalSparklineWithOneValueAndUseDefaultOrderWithColumn()
@@ -79,10 +85,10 @@ class SparklinesConfigTest extends IntegrationTestCase
$params['columns'] = 'nb_visits';
$this->config->addSparkline($params, $value = 10, $description = 'Visits');
- $expectedSparkline = array('column' => 'nb_visits', 'value' => 10, 'description' => 'Visits');
+ $expectedSparkline = array('value' => 10, 'description' => 'Visits', 'column' => 'nb_visits');
$sparklines = $this->config->getSortedSparklines();
- $this->assertSame(array($expectedSparkline), $sparklines[0]['metrics']);
+ $this->assertSame(array($expectedSparkline), $sparklines[''][0]['metrics']['']);
}
public function test_addSparkline_shouldAddSparklineWithMultipleValues()
@@ -92,9 +98,9 @@ class SparklinesConfigTest extends IntegrationTestCase
$sparklines = $this->config->getSortedSparklines();
$this->assertSame(array (
- array ('column' => '', 'value' => 10, 'description' => 'Visits'),
- array ('column' => '', 'value' => 20, 'description' => 'Actions'),
- ), $sparklines[0]['metrics']);
+ array ('value' => 10, 'description' => 'Visits', 'column' => ''),
+ array ('value' => 20, 'description' => 'Actions', 'column' => ''),
+ ), $sparklines[''][0]['metrics']['']);
}
public function test_addSparkline_shouldAddSparklinesMultipleValuesWithColumns()
@@ -105,12 +111,12 @@ class SparklinesConfigTest extends IntegrationTestCase
$this->config->addSparkline($params, $values = array(10, 20), $description = array('Visits', 'Actions'));
$expectedSparkline = array(
- array ('column' => 'nb_visits', 'value' => 10, 'description' => 'Visits'),
- array ('column' => 'nb_actions', 'value' => 20, 'description' => 'Actions')
+ array ('value' => 10, 'description' => 'Visits', 'column' => 'nb_visits'),
+ array ('value' => 20, 'description' => 'Actions', 'column' => 'nb_actions')
);
$sparklines = $this->config->getSortedSparklines();
- $this->assertSame($expectedSparkline, $sparklines[0]['metrics']);
+ $this->assertSame($expectedSparkline, $sparklines[''][0]['metrics']['']);
}
/**
@@ -133,7 +139,7 @@ class SparklinesConfigTest extends IntegrationTestCase
$this->assertSame(array (
'percent' => '-52.4%',
'tooltip' => '1 visit compared to 2 visits'
- ), $sparklines[0]['evolution']);
+ ), $sparklines[''][0]['evolution']);
}
public function test_addSparkline_shouldAddOrder()
@@ -142,7 +148,7 @@ class SparklinesConfigTest extends IntegrationTestCase
$sparklines = $this->config->getSortedSparklines();
- $this->assertSame(42, $sparklines[0]['order']);
+ $this->assertSame(42, $sparklines[''][0]['order']);
}
public function test_addSparkline_shouldBeAbleToBuildSparklineUrlBasedOnGETparams()
@@ -154,7 +160,166 @@ class SparklinesConfigTest extends IntegrationTestCase
$sparklines = $this->config->getSortedSparklines();
- $this->assertSame('?columns=nb_visits&viewDataTable=sparkline&date=2012-03-06,2012-04-04', $sparklines[0]['url']);
+ $this->assertSame('?columns=nb_visits&viewDataTable=sparkline&date=2012-03-06,2012-04-04', $sparklines[''][0]['url']);
+ }
+
+ public function test_addSparkline_shouldAddSparklinesWithGroups()
+ {
+ $this->config->addSparkline($this->sparklineParams(), $value = 10, $description = 'Visits', $evolution = null, $order = '4', $title = 'title1', $group = 'one');
+ $this->config->addSparkline($this->sparklineParams(), $value = 11, $description = 'Visits1', $evolution = null, $order = '1', $title = 'title2', $group = 'one');
+ $this->config->addSparkline($this->sparklineParams(), $value = 12, $description = 'Visits2', $evolution = null, $order = '3', $title = 'title3', $group = 'two');
+ $this->config->addSparkline($this->sparklineParams(), $value = 13, $description = 'Visits3', $evolution = null, $order = '6', $title = 'title4', $group = 'two');
+
+ $sparklines = $this->config->getSortedSparklines();
+ $expectedSparklines = [
+ 'one' => [
+ [
+ 'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline',
+ 'metrics' => [
+ '' => [
+ 0 => [
+ 'value' => 11,
+ 'description' => 'Visits1',
+ 'column' => '',
+ ],
+ ],
+ ],
+ 'order' => 1,
+ 'title' => 'title2',
+ 'group' => 'one',
+ 'seriesIndices' => null,
+ 'graphParams' => null,
+ ],
+ [
+ 'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline',
+ 'metrics' => [
+ '' => [
+ 0 => [
+ 'value' => 10,
+ 'description' => 'Visits',
+ 'column' => '',
+ ],
+ ],
+ ],
+ 'order' => 4,
+ 'title' => 'title1',
+ 'group' => 'one',
+ 'seriesIndices' => null,
+ 'graphParams' => null,
+ ],
+ ],
+ 'two' => [
+ [
+ 'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline',
+ 'metrics' => [
+ '' => [
+ 0 => [
+ 'value' => 12,
+ 'description' => 'Visits2',
+ 'column' => '',
+ ],
+ ],
+ ],
+ 'order' => 3,
+ 'title' => 'title3',
+ 'group' => 'two',
+ 'seriesIndices' => null,
+ 'graphParams' => null,
+ ],
+ [
+ 'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline',
+ 'metrics' => [
+ '' => [
+ 0 => [
+ 'value' => 13,
+ 'description' => 'Visits3',
+ 'column' => '',
+ ],
+ ],
+ ],
+ 'order' => 6,
+ 'title' => 'title4',
+ 'group' => 'two',
+ 'seriesIndices' => null,
+ 'graphParams' => null,
+ ],
+ ],
+ ];
+
+ $this->assertSame($expectedSparklines, $sparklines);
+ }
+
+ public function test_addSparkline_shouldAddSparklineMetricsWithGroups()
+ {
+ $metricInfos = [
+ [
+ 'value' => 'v1',
+ 'description' => 'd1',
+ 'group' => 'g1',
+ ],
+ [
+ 'value' => 'v2',
+ 'description' => 'd3',
+ 'group' => 'g1',
+ ],
+ [
+ 'value' => 'v3',
+ 'description' => 'd3',
+ 'group' => 'g3',
+ ],
+ [
+ 'value' => 'v4',
+ 'description' => 'd4',
+ 'group' => 'g1',
+ ],
+ ];
+ $this->config->addSparkline($this->sparklineParams(), $metricInfos, $description = null);
+
+ $sparklines = $this->config->getSortedSparklines();
+ $expectedSparklines = [
+ '' => [
+ [
+ 'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline',
+ 'metrics' => [
+ 'g1' => [
+ 0 => [
+ 'value' => 'v1',
+ 'description' => 'd1',
+ 'group' => 'g1',
+ 'column' => '',
+ ],
+ 1 => [
+ 'value' => 'v2',
+ 'description' => 'd3',
+ 'group' => 'g1',
+ 'column' => '',
+ ],
+ 2 => [
+ 'value' => 'v4',
+ 'description' => 'd4',
+ 'group' => 'g1',
+ 'column' => '',
+ ],
+ ],
+ 'g3' => [
+ 0 => [
+ 'value' => 'v3',
+ 'description' => 'd3',
+ 'group' => 'g3',
+ 'column' => '',
+ ],
+ ],
+ ],
+ 'order' => 999,
+ 'title' => null,
+ 'group' => '',
+ 'seriesIndices' => null,
+ 'graphParams' => null,
+ ],
+ ],
+ ];
+
+ $this->assertSame($expectedSparklines, $sparklines);
}
private function sparklineParams($params = array())
diff --git a/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php b/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php
index 1134df2f5c..e4759845fa 100644
--- a/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php
+++ b/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php
@@ -49,9 +49,9 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->addFewSparklines();
$this->assertSame(array(
- array('columns' => 'nb_visits', 'order' => null),
- array('columns' => 'nb_unique_visitors', 'order' => 99),
- array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null),
+ array('columns' => 'nb_visits', 'order' => null, 'graphParams' => null),
+ array('columns' => 'nb_unique_visitors', 'order' => 99, 'graphParams' => null),
+ array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null, 'graphParams' => null),
), $this->config->getSparklineMetrics());
}
@@ -62,8 +62,8 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->config->removeSparklineMetric('nb_unique_visitors');
$this->assertSame(array(
- array('columns' => 'nb_visits', 'order' => null),
- array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null),
+ array('columns' => 'nb_visits', 'order' => null, 'graphParams' => null),
+ array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null, 'graphParams' => null),
), $this->config->getSparklineMetrics());
}
@@ -74,8 +74,8 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->config->removeSparklineMetric(array('nb_downloads', 'nb_outlinks'));
$this->assertSame(array(
- array('columns' => 'nb_visits', 'order' => null),
- array('columns' => 'nb_unique_visitors', 'order' => 99),
+ array('columns' => 'nb_visits', 'order' => null, 'graphParams' => null),
+ array('columns' => 'nb_unique_visitors', 'order' => 99, 'graphParams' => null),
), $this->config->getSparklineMetrics());
}
@@ -86,9 +86,9 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->config->replaceSparklineMetric('nb_unique_visitors', '');
$this->assertSame(array(
- array('columns' => 'nb_visits', 'order' => null),
- array('columns' => '', 'order' => 99),
- array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null),
+ array('columns' => 'nb_visits', 'order' => null, 'graphParams' => null),
+ array('columns' => '', 'order' => 99, 'graphParams' => null),
+ array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null, 'graphParams' => null),
), $this->config->getSparklineMetrics());
}
@@ -99,9 +99,9 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->config->replaceSparklineMetric(array('nb_downloads', 'nb_outlinks'), '');
$this->assertSame(array(
- array('columns' => 'nb_visits', 'order' => null),
- array('columns' => 'nb_unique_visitors', 'order' => 99),
- array('columns' => '', 'order' => null),
+ array('columns' => 'nb_visits', 'order' => null, 'graphParams' => null),
+ array('columns' => 'nb_unique_visitors', 'order' => 99, 'graphParams' => null),
+ array('columns' => '', 'order' => null, 'graphParams' => null),
), $this->config->getSparklineMetrics());
}
@@ -113,10 +113,10 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->config->addPlaceholder($order = 3);
$this->assertSame(array(
- array('url' => '', 'metrics' => array(), 'order' => 3),
- array('url' => '', 'metrics' => array(), 'order' => 10),
- array('url' => '', 'metrics' => array(), 'order' => 999),
- array('url' => '', 'metrics' => array(), 'order' => 1001),
+ 'placeholder3' => [['url' => '', 'metrics' => array(), 'order' => 3, 'group' => 'placeholder3']],
+ 'placeholder1' => [['url' => '', 'metrics' => array(), 'order' => 10, 'group' => 'placeholder1']],
+ 'placeholder0' => [['url' => '', 'metrics' => array(), 'order' => 999, 'group' => 'placeholder0']],
+ 'placeholder2' => [['url' => '', 'metrics' => array(), 'order' => 1001, 'group' => 'placeholder2']],
), $this->config->getSortedSparklines());
}
@@ -126,5 +126,4 @@ class SparklinesConfigTest extends \PHPUnit_Framework_TestCase
$this->config->addSparklineMetric('nb_unique_visitors', 99);
$this->config->addSparklineMetric(array('nb_downloads', 'nb_outlinks'));
}
-
}