diff options
Diffstat (limited to 'plugins/CoreVisualizations')
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"> </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"> + + </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 %}" + > {{ 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')); } - } |