/** * Given the Row evolution dataTable, and the associated metadata, * enriches the metadata with min/max values, and % change between the first period and the last one * @param array $metadata * @param DataTable\Map $dataTable */ private function enhanceRowEvolutionMetaData(&$metadata, $dataTable) { // prepare result array for metrics $metricsResult = array(); foreach ($metadata['metrics'] as $metric => $name) { $metricsResult[$metric] = array('name' => $name); if (!empty($metadata['logos'][$metric])) { $metricsResult[$metric]['logo'] = $metadata['logos'][$metric]; } } unset($metadata['logos']); $subDataTables = $dataTable->getDataTables(); $firstDataTable = reset($subDataTables); $firstDataTableRow = $firstDataTable->getFirstRow(); $lastDataTable = end($subDataTables); $lastDataTableRow = $lastDataTable->getFirstRow(); // Process min/max values $firstNonZeroFound = array(); foreach ($subDataTables as $subDataTable) { // $subDataTable is the report for one period, it has only one row $firstRow = $subDataTable->getFirstRow(); foreach ($metadata['metrics'] as $metric => $label) { $value = $firstRow ? floatval($firstRow->getColumn($metric)) : 0; if ($value > 0) { $firstNonZeroFound[$metric] = true; } else { if (!isset($firstNonZeroFound[$metric])) { continue; } } if (!isset($metricsResult[$metric]['min']) || $metricsResult[$metric]['min'] > $value) { $metricsResult[$metric]['min'] = $value; } if (!isset($metricsResult[$metric]['max']) || $metricsResult[$metric]['max'] < $value) { $metricsResult[$metric]['max'] = $value; } } } // Process % change between first/last values foreach ($metadata['metrics'] as $metric => $label) { $first = $firstDataTableRow ? floatval($firstDataTableRow->getColumn($metric)) : 0; $last = $lastDataTableRow ? floatval($lastDataTableRow->getColumn($metric)) : 0; // do not calculate evolution if the first value is 0 (to avoid divide-by-zero) if ($first == 0) { continue; } $change = CalculateEvolutionFilter::calculate($last, $first, $quotientPrecision = 0); $change = CalculateEvolutionFilter::prependPlusSignToNumber($change); $metricsResult[$metric]['change'] = $change; } $metadata['metrics'] = $metricsResult; }
/** * Calculates the evolution from one value to another and returns HTML displaying * the evolution percent. The HTML includes an up/down arrow and is colored red, black or * green depending on whether the evolution is negative, 0 or positive. * * No HTML is returned if the current value and evolution percent are both 0. * * @param string $date The date of the current value. * @param int $currentValue The value to calculate evolution to. * @param string $pastDate The date of past value. * @param int $pastValue The value in the past to calculate evolution from. * @return string|false The HTML or `false` if the evolution is 0 and the current value is 0. * @api */ protected function getEvolutionHtml($date, $currentValue, $pastDate, $pastValue) { $evolutionPercent = CalculateEvolutionFilter::calculate($currentValue, $pastValue, $precision = 1); // do not display evolution if evolution percent is 0 and current value is 0 if ($evolutionPercent == 0 && $currentValue == 0) { return false; } $titleEvolutionPercent = $evolutionPercent; if ($evolutionPercent < 0) { $class = "negative-evolution"; $img = "arrow_down.png"; } else { if ($evolutionPercent == 0) { $class = "neutral-evolution"; $img = "stop.png"; } else { $class = "positive-evolution"; $img = "arrow_up.png"; $titleEvolutionPercent = '+' . $titleEvolutionPercent; } } $title = Piwik::translate('General_EvolutionSummaryGeneric', array(Piwik::translate('General_NVisits', $currentValue), $date, Piwik::translate('General_NVisits', $pastValue), $pastDate, $titleEvolutionPercent)); $result = '<span class="metricEvolution" title="' . $title . '"><img style="padding-right:4px" src="plugins/MultiSites/images/' . $img . '"/><strong'; if (isset($class)) { $result .= ' class="' . $class . '"'; } $result .= '>' . $evolutionPercent . '</strong></span>'; return $result; }
/** * Utility method that calculates evolution values for a set of current & past values * and sets properties on a View w/ HTML that displays the evolution percents. * * @param string $date The date of the current values. * @param array $currentValues Array mapping view property names w/ present values. * @param string $lastPeriodDate The date of the period in the past. * @param array $previousValues Array mapping view property names w/ past values. Keys * in this array should be the same as keys in $currentValues. * @return array Added current values */ private function addEvolutionPropertiesToView($date, $currentValues, $lastPeriodDate, $previousValues) { foreach ($previousValues as $name => $pastValue) { $currentValue = $currentValues[$name]; $evolutionName = $name . 'Evolution'; $currentValueFormatted = NumberFormatter::getInstance()->format($currentValue); $pastValueFormatted = NumberFormatter::getInstance()->format($pastValue); $currentValues[$evolutionName] = array('currentValue' => $currentValue, 'pastValue' => $pastValue, 'tooltip' => Piwik::translate('General_EvolutionSummaryGeneric', array(Piwik::translate('General_NVisits', $currentValueFormatted), $date, Piwik::translate('General_NVisits', $pastValueFormatted), $lastPeriodDate, CalculateEvolutionFilter::calculate($currentValue, $pastValue, $precision = 1)))); } return $currentValues; }
/** * Sets the total evolution metadata for a datatable returned by $this->buildDataTable * given data for the last period. * * @param DataTable|DataTable\Map $dataTable * @param DataTable|DataTable\Map $pastData * @param array $apiMetrics Metrics info. */ private function setPastDataMetadata($dataTable, $pastData, $apiMetrics) { if ($dataTable instanceof DataTable\Map) { $pastArray = $pastData->getDataTables(); foreach ($dataTable->getDataTables() as $subTable) { $this->setPastDataMetadata($subTable, current($pastArray), $apiMetrics); next($pastArray); } } else { // calculate total visits/actions/revenue for past data $this->setMetricsTotalsMetadata($pastData, $apiMetrics); foreach ($apiMetrics as $label => $metricInfo) { // get the names of metadata to set $totalMetadataName = self::getTotalMetadataName($label); $lastPeriodTotalMetadataName = self::getLastPeriodMetadataName($totalMetadataName); $totalEvolutionMetadataName = self::getTotalMetadataName($metricInfo[self::METRIC_EVOLUTION_COL_NAME_KEY]); // set last period total $pastTotal = $pastData->getMetadata($totalMetadataName); $dataTable->setMetadata($lastPeriodTotalMetadataName, $pastTotal); // calculate & set evolution $currentTotal = $dataTable->getMetadata($totalMetadataName); $evolution = CalculateEvolutionFilter::calculate($currentTotal, $pastTotal); $dataTable->setMetadata($totalEvolutionMetadataName, $evolution); } } }
/** * Add a new sparkline to be displayed to the view. * * Each sparkline can consist of one or multiple metrics. One metric consists of a value and a description. By * default the value is shown first, then the description. The description can optionally contain a '%s' in case * the value shall be displayed within the description. If multiple metrics are given, they will be separated by * a comma. * * @param array $requestParamsForSparkline You need to at least set a module / action eg * array('columns' => array('nb_visit'), 'module' => '', 'action' => '') * @param int|float|string|array $value Either the metric value or an array of values. * @param string|array $description Either one description or an array of descriptions. If an array, both * $value and $description need the same amount of array entries. * $description[0] should be the description for $value[0]. * $description should be already translated. If $value should appear * somewhere within the text a `%s` can be used in the translation. * @param array|null $evolution Optional array containing at least the array keys 'currentValue' and * 'pastValue' which are needed to calculate the correct percentage. * An optional 'tooltip' can be set as well. Eg * array('currentValue' => 10, 'pastValue' => 20, * 'tooltip' => '10 visits in 2015-07-26 compared to 20 visits in 2015-07-25') * @param int $order Defines the order. The lower the order the earlier the sparkline will be * displayed. By default the sparkline will be appended to the end. * @throws \Exception In case an evolution parameter is set but has wrong data structure */ public function addSparkline($requestParamsForSparkline, $value, $description, $evolution = null, $order = null) { $metrics = array(); if (is_array($value)) { $values = $value; } else { $values = array($value); } if (!is_array($description)) { $description = array($description); } if (!empty($requestParamsForSparkline['columns']) && is_array($requestParamsForSparkline['columns']) && count($requestParamsForSparkline['columns']) === count($values)) { $columns = array_values($requestParamsForSparkline['columns']); } elseif (!empty($requestParamsForSparkline['columns']) && is_string($requestParamsForSparkline['columns']) && count($values) === 1) { $columns = array($requestParamsForSparkline['columns']); } else { $columns = array(); } if (count($values) === count($description)) { foreach ($values as $index => $value) { $metrics[] = array('column' => isset($columns[$index]) ? $columns[$index] : '', 'value' => $value, 'description' => $description[$index]); } } else { $msg = 'The number of values and descriptions need to be the same to add a sparkline. '; $msg .= 'Values: ' . implode(', ', $values) . ' Descriptions: ' . implode(', ', $description); throw new \Exception($msg); } if (empty($metrics)) { return; } $sparkline = array('url' => $this->getUrlSparkline($requestParamsForSparkline), 'metrics' => $metrics, 'order' => $this->getSparklineOrder($order)); if (!empty($evolution)) { if (!is_array($evolution) || !array_key_exists('currentValue', $evolution) || !array_key_exists('pastValue', $evolution)) { throw new \Exception('In order to show an evolution in the sparklines view a currentValue and pastValue array key needs to be present'); } $evolutionPercent = CalculateEvolutionFilter::calculate($evolution['currentValue'], $evolution['pastValue'], $precision = 1); // do not display evolution if evolution percent is 0 and current value is 0 if ($evolutionPercent != 0 || $evolution['currentValue'] != 0) { $sparkline['evolution'] = array('percent' => $evolutionPercent, 'tooltip' => !empty($evolution['tooltip']) ? $evolution['tooltip'] : null); } } $this->sparklines[] = $sparkline; }