PDF rausgenommen
This commit is contained in:
@ -0,0 +1,49 @@
|
||||
<div
|
||||
class="jqplot-seriespicker"
|
||||
ng-class="{open: $ctrl.isPopupVisible}"
|
||||
ng-mouseenter="$ctrl.isPopupVisible = true"
|
||||
ng-mouseleave="$ctrl.onLeavePopup()"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
ng-click="$event.preventDefault(); $event.stopPropagation();"
|
||||
>
|
||||
+
|
||||
</a>
|
||||
<div
|
||||
class="jqplot-seriespicker-popover"
|
||||
ng-if="$ctrl.isPopupVisible"
|
||||
>
|
||||
<p class="headline">{{ ($ctrl.multiselect ? 'General_MetricsToPlot' : 'General_MetricToPlot') | translate }}</p>
|
||||
<p
|
||||
ng-repeat="columnConfig in $ctrl.selectableColumns"
|
||||
class="pickColumn"
|
||||
ng-click="$ctrl.optionSelected(columnConfig.column, $ctrl.columnStates)"
|
||||
>
|
||||
<input
|
||||
class="select"
|
||||
ng-checked="$ctrl.columnStates[columnConfig.column]"
|
||||
ng-attr-type="{{ $ctrl.multiselect ? 'checkbox' : 'radio' }}"
|
||||
/>
|
||||
<label>{{ columnConfig.translation }}</label>
|
||||
</p>
|
||||
<p
|
||||
ng-if="$ctrl.selectableRows.length"
|
||||
class="headline recordsToPlot"
|
||||
>
|
||||
{{ 'General_RecordsToPlot' | translate }}
|
||||
</p>
|
||||
<p
|
||||
ng-repeat="rowConfig in $ctrl.selectableRows"
|
||||
class="pickRow"
|
||||
ng-click="$ctrl.optionSelected(rowConfig.matcher, $ctrl.rowStates)"
|
||||
>
|
||||
<input
|
||||
class="select"
|
||||
ng-checked="$ctrl.rowStates[rowConfig.matcher]"
|
||||
ng-attr-type="{{ $ctrl.multiselect ? 'checkbox' : 'radio' }}"
|
||||
/>
|
||||
<label>{{ rowConfig.label }}</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,144 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* This series picker component is a popup that displays a list of metrics/row
|
||||
* values that can be selected. It's used by certain datatable visualizations
|
||||
* to allow users to select different data series for display.
|
||||
*
|
||||
* Inputs:
|
||||
* - multiselect: true if the picker should allow selecting multiple items, false
|
||||
* if otherwise.
|
||||
* - selectableColumns: the list of selectable metric values. must be a list of
|
||||
* objects with the following properties:
|
||||
* * column: the ID of the column, eg, nb_visits
|
||||
* * translation: the translated text for the column, eg, Visits
|
||||
* - selectableRows: the list of selectable row values. must be a list of objects
|
||||
* with the following properties:
|
||||
* * matcher: the ID of the row
|
||||
* * label: the display text for the row
|
||||
* - selectedColumns: the list of selected columns. should be a list of strings
|
||||
* that correspond to the 'column' property in selectableColumns.
|
||||
* - selectedRows: the list of selected rows. should be a list of strings that
|
||||
* correspond to the 'matcher' property in selectableRows.
|
||||
* - onSelect: expression invoked when a user makes a new selection. invoked
|
||||
* with the following local variables:
|
||||
* * columns: list of IDs of new selected columns, if any
|
||||
* * rows: list of matchers of new selected rows, if any
|
||||
*
|
||||
* Usage:
|
||||
* <piwik-series-picker />
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').component('piwikSeriesPicker', {
|
||||
templateUrl: 'plugins/CoreVisualizations/angularjs/series-picker/series-picker.component.html?cb=' + piwik.cacheBuster,
|
||||
bindings: {
|
||||
multiselect: '<',
|
||||
selectableColumns: '<',
|
||||
selectableRows: '<',
|
||||
selectedColumns: '<',
|
||||
selectedRows: '<',
|
||||
onSelect: '&'
|
||||
},
|
||||
controller: SeriesPickerController
|
||||
});
|
||||
|
||||
SeriesPickerController.$inject = [];
|
||||
|
||||
function SeriesPickerController() {
|
||||
var vm = this;
|
||||
vm.isPopupVisible = false;
|
||||
|
||||
// note: column & row states are separated since it's technically possible (though
|
||||
// highly improbable) that a row value matcher will be the same as a recognized column.
|
||||
vm.columnStates = {};
|
||||
vm.rowStates = {};
|
||||
vm.optionSelected = optionSelected;
|
||||
vm.onLeavePopup = onLeavePopup;
|
||||
vm.$onInit = $onInit;
|
||||
|
||||
function $onInit() {
|
||||
vm.columnStates = getInitialOptionStates(vm.selectableColumns, vm.selectedColumns);
|
||||
vm.rowStates = getInitialOptionStates(vm.selectableRows, vm.selectedRows);
|
||||
}
|
||||
|
||||
function getInitialOptionStates(allOptions, selectedOptions) {
|
||||
var states = {};
|
||||
|
||||
allOptions.forEach(function (columnConfig) {
|
||||
states[columnConfig.column || columnConfig.matcher] = false;
|
||||
});
|
||||
|
||||
selectedOptions.forEach(function (column) {
|
||||
states[column] = true;
|
||||
});
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
function optionSelected(optionValue, optionStates) {
|
||||
if (!vm.multiselect) {
|
||||
unselectOptions(vm.columnStates);
|
||||
unselectOptions(vm.rowStates);
|
||||
}
|
||||
|
||||
optionStates[optionValue] = !optionStates[optionValue];
|
||||
|
||||
if (optionStates[optionValue]) {
|
||||
triggerOnSelectAndClose();
|
||||
}
|
||||
}
|
||||
|
||||
function onLeavePopup() {
|
||||
vm.isPopupVisible = false;
|
||||
|
||||
if (optionsChanged()) {
|
||||
triggerOnSelectAndClose();
|
||||
}
|
||||
}
|
||||
|
||||
function triggerOnSelectAndClose() {
|
||||
if (!vm.onSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.isPopupVisible = false;
|
||||
|
||||
vm.onSelect({
|
||||
columns: getSelected(vm.columnStates),
|
||||
rows: getSelected(vm.rowStates)
|
||||
});
|
||||
}
|
||||
|
||||
function optionsChanged() {
|
||||
return !arrayEqual(getSelected(vm.columnStates), vm.selectedColumns)
|
||||
|| !arrayEqual(getSelected(vm.rowStates), vm.selectedRows);
|
||||
}
|
||||
|
||||
function arrayEqual(lhs, rhs) {
|
||||
if (lhs.length !== rhs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lhs
|
||||
.filter(function (element) { return rhs.indexOf(element) === -1; })
|
||||
.length === 0;
|
||||
}
|
||||
|
||||
function unselectOptions(optionStates) {
|
||||
Object.keys(optionStates).forEach(function (optionName) {
|
||||
optionStates[optionName] = false;
|
||||
});
|
||||
}
|
||||
|
||||
function getSelected(optionStates) {
|
||||
return Object.keys(optionStates).filter(function (optionName) {
|
||||
return !! optionStates[optionName];
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,28 @@
|
||||
piwik-series-picker {
|
||||
display: inline-block;
|
||||
|
||||
.jqplot-seriespicker {
|
||||
&:not(.open) {
|
||||
opacity: .55;
|
||||
}
|
||||
|
||||
&.open { // while open, make sure we're above other series picker icons
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.jqplot-seriespicker-popover {
|
||||
position: absolute;
|
||||
|
||||
top: -3px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<div class="singleMetricView" ng-class="{'loading': $ctrl.isLoading}">
|
||||
<piwik-sparkline
|
||||
class="metric-sparkline"
|
||||
params="$ctrl.sparklineParams"
|
||||
>
|
||||
</piwik-sparkline>
|
||||
<div class="metric-value">
|
||||
<span title="{{ $ctrl.metricDocumentation }}">
|
||||
<strong>{{ $ctrl.metricValue }}</strong> {{ ($ctrl.metricTranslation || '').toLowerCase() }}
|
||||
</span>
|
||||
<span class="metricEvolution"
|
||||
ng-if="$ctrl.pastValue !== null"
|
||||
title="{{ 'General_EvolutionSummaryGeneric'|translate:$ctrl.metricValue:$ctrl.getCurrentPeriod():$ctrl.pastValue:$ctrl.pastPeriod:$ctrl.metricChangePercent }}"
|
||||
>
|
||||
<span ng-class="{'positive-evolution': $ctrl.metricValueUnformatted > $ctrl.pastValueUnformatted, 'negative-evolution': $ctrl.metricValueUnformatted < $ctrl.pastValueUnformatted}">
|
||||
{{ $ctrl.metricChangePercent }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,279 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <piwik-single-metric-view>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').component('piwikSingleMetricView', {
|
||||
templateUrl: 'plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.html?cb=' + piwik.cacheBuster,
|
||||
bindings: {
|
||||
metric: '<',
|
||||
idGoal: '<',
|
||||
metricTranslations: '<',
|
||||
metricDocumentations: '<',
|
||||
goals: '<',
|
||||
goalMetrics: '<'
|
||||
},
|
||||
controller: SingleMetricViewController
|
||||
});
|
||||
|
||||
SingleMetricViewController.$inject = ['piwik', 'piwikApi', '$element', '$httpParamSerializer', '$compile', '$scope', 'piwikPeriods', '$q'];
|
||||
|
||||
function SingleMetricViewController(piwik, piwikApi, $element, $httpParamSerializer, $compile, $scope, piwikPeriods, $q) {
|
||||
var seriesPickerScope;
|
||||
|
||||
var vm = this;
|
||||
vm.metricValue = null;
|
||||
vm.isLoading = false;
|
||||
vm.metricTranslation = null;
|
||||
vm.metricDocumentation = null;
|
||||
vm.selectableColumns = [];
|
||||
vm.responses = null;
|
||||
vm.sparklineParams = {};
|
||||
vm.$onInit = $onInit;
|
||||
vm.$onChanges = $onChanges;
|
||||
vm.$onDestroy = $onDestroy;
|
||||
vm.getCurrentPeriod = getCurrentPeriod;
|
||||
vm.getMetricTranslation = getMetricTranslation;
|
||||
vm.setMetric = setMetric;
|
||||
|
||||
function setSparklineParams() {
|
||||
var params = { module: 'API', action: 'get', columns: vm.metric };
|
||||
if (isIdGoalSet()) {
|
||||
params.idGoal = vm.idGoal;
|
||||
params.module = 'Goals';
|
||||
}
|
||||
vm.sparklineParams = params;
|
||||
}
|
||||
|
||||
function $onInit() {
|
||||
vm.selectedColumns = [vm.metric];
|
||||
if (piwik.period !== 'range') {
|
||||
vm.pastPeriod = getPastPeriodStr();
|
||||
}
|
||||
|
||||
setSelectableColumns();
|
||||
|
||||
createSeriesPicker();
|
||||
|
||||
$element.closest('.widgetContent')
|
||||
.on('widget:destroy', function() { $scope.$parent.$destroy(); })
|
||||
.on('widget:reload', function() { $scope.$parent.$destroy(); });
|
||||
|
||||
setSparklineParams();
|
||||
}
|
||||
|
||||
function $onChanges(changes) {
|
||||
if (changes.metric && changes.metric.previousValue !== changes.metric.currentValue) {
|
||||
onMetricChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function $onDestroy() {
|
||||
$element.closest('.widgetContent').off('widget:destroy').off('widget:reload');
|
||||
destroySeriesPicker();
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
vm.isLoading = true;
|
||||
|
||||
var promises = [];
|
||||
|
||||
var apiModule = 'API';
|
||||
var apiAction = 'get';
|
||||
|
||||
var extraParams = {};
|
||||
if (isIdGoalSet()) {
|
||||
extraParams.idGoal = vm.idGoal;
|
||||
// the conversion rate added by the AddColumnsProcessedMetrics filter conflicts w/ the goals one, so don't run it
|
||||
extraParams.filter_add_columns_when_show_all_columns = 0;
|
||||
|
||||
apiModule = 'Goals';
|
||||
apiAction = 'get';
|
||||
}
|
||||
|
||||
// first request for formatted data
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
format_metrics: 'all'
|
||||
}, extraParams)));
|
||||
|
||||
if (piwik.period !== 'range') {
|
||||
// second request for unformatted data so we can calculate evolution
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
format_metrics: '0'
|
||||
}, extraParams)));
|
||||
|
||||
// third request for past data (unformatted)
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
date: getLastPeriodDate(),
|
||||
format_metrics: '0',
|
||||
}, extraParams)));
|
||||
|
||||
// fourth request for past data (formatted for tooltip display)
|
||||
promises.push(piwikApi.fetch($.extend({
|
||||
method: apiModule + '.' + apiAction,
|
||||
date: getLastPeriodDate(),
|
||||
format_metrics: 'all',
|
||||
}, extraParams)));
|
||||
}
|
||||
|
||||
return $q.all(promises).then(function (responses) {
|
||||
vm.responses = responses;
|
||||
vm.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function recalculateValues() {
|
||||
// update display based on processed report metadata
|
||||
setWidgetTitle();
|
||||
vm.metricDocumentation = getMetricDocumentation();
|
||||
|
||||
// update data
|
||||
var currentData = vm.responses[0];
|
||||
vm.metricValue = currentData[vm.metric] || 0;
|
||||
|
||||
if (vm.responses[1]) {
|
||||
vm.metricValueUnformatted = vm.responses[1][vm.metric];
|
||||
|
||||
var pastData = vm.responses[2];
|
||||
vm.pastValueUnformatted = pastData[vm.metric] || 0;
|
||||
|
||||
var evolution = piwik.helper.calculateEvolution(vm.metricValueUnformatted, vm.pastValueUnformatted);
|
||||
vm.metricChangePercent = (evolution * 100).toFixed(2) + ' %';
|
||||
|
||||
var pastDataFormatted = vm.responses[3];
|
||||
vm.pastValue = pastDataFormatted[vm.metric] || 0;
|
||||
} else {
|
||||
vm.pastValue = null;
|
||||
vm.metricChangePercent = null;
|
||||
}
|
||||
|
||||
// don't change the metric translation until data is fetched to avoid loading state confusion
|
||||
vm.metricTranslation = getMetricTranslation();
|
||||
}
|
||||
|
||||
function getLastPeriodDate() {
|
||||
var RangePeriod = piwikPeriods.get('range');
|
||||
var result = RangePeriod.getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
|
||||
return $.datepicker.formatDate('yy-mm-dd', result);
|
||||
}
|
||||
|
||||
function setWidgetTitle() {
|
||||
var title = vm.getMetricTranslation();
|
||||
if (isIdGoalSet()) {
|
||||
var goalName = vm.goals[vm.idGoal].name;
|
||||
title = goalName + ' - ' + title;
|
||||
}
|
||||
|
||||
$element.closest('div.widget').find('.widgetTop > .widgetName > span').text(title);
|
||||
}
|
||||
|
||||
function getCurrentPeriod() {
|
||||
if (piwik.startDateString === piwik.endDateString) {
|
||||
return piwik.endDateString;
|
||||
}
|
||||
return piwik.startDateString + ', ' + piwik.endDateString;
|
||||
}
|
||||
|
||||
function createSeriesPicker() {
|
||||
vm.selectedColumns = [vm.idGoal ? ('goal' + vm.idGoal + '_' + vm.metric) : vm.metric];
|
||||
|
||||
var $widgetName = $element.closest('div.widget').find('.widgetTop > .widgetName');
|
||||
|
||||
var $seriesPicker = $('<piwik-series-picker class="single-metric-view-picker" multiselect="false" ' +
|
||||
'selectable-columns="$ctrl.selectableColumns" selectable-rows="[]" selected-columns="$ctrl.selectedColumns" ' +
|
||||
'selected-rows="[]" on-select="$ctrl.setMetric(columns[0])" />');
|
||||
|
||||
seriesPickerScope = $scope.$new();
|
||||
$compile($seriesPicker)(seriesPickerScope);
|
||||
|
||||
$widgetName.append($seriesPicker);
|
||||
}
|
||||
|
||||
function destroySeriesPicker() {
|
||||
$element.closest('div.widget').find('.single-metric-view-picker').remove();
|
||||
|
||||
seriesPickerScope.$destroy();
|
||||
seriesPickerScope = null;
|
||||
}
|
||||
|
||||
function getMetricDocumentation() {
|
||||
if (!vm.metricDocumentations || !vm.metricDocumentations[vm.metric]) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return vm.metricDocumentations[vm.metric];
|
||||
}
|
||||
|
||||
function getMetricTranslation() {
|
||||
if (!vm.metricTranslations || !vm.metricTranslations[vm.metric]) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return vm.metricTranslations[vm.metric];
|
||||
}
|
||||
|
||||
function setSelectableColumns() {
|
||||
var result = [];
|
||||
Object.keys(vm.metricTranslations).forEach(function (column) {
|
||||
result.push({ column: column, translation: vm.metricTranslations[column] });
|
||||
});
|
||||
|
||||
Object.keys(vm.goals).forEach(function (idgoal) {
|
||||
var goal = vm.goals[idgoal];
|
||||
vm.goalMetrics.forEach(function (column) {
|
||||
result.push({
|
||||
column: 'goal' + goal.idgoal + '_' + column,
|
||||
translation: goal.name + ' - ' + vm.metricTranslations[column]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
vm.selectableColumns = result;
|
||||
}
|
||||
|
||||
function onMetricChanged() {
|
||||
setSparklineParams();
|
||||
|
||||
fetchData().then(recalculateValues);
|
||||
|
||||
// notify widget of parameter change so it is replaced
|
||||
$element.closest('[widgetId]').trigger('setParameters', { column: vm.metric, idGoal: vm.idGoal });
|
||||
}
|
||||
|
||||
function setMetric(newColumn) {
|
||||
var idGoal;
|
||||
|
||||
var m = newColumn.match(/^goal([0-9]+)_(.*)/);
|
||||
if (m) {
|
||||
idGoal = +m[1];
|
||||
newColumn = m[2];
|
||||
}
|
||||
|
||||
if (vm.metric !== newColumn || idGoal !== vm.idGoal) {
|
||||
vm.metric = newColumn;
|
||||
vm.idGoal = idGoal;
|
||||
onMetricChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function getPastPeriodStr() {
|
||||
var startDate = piwikPeriods.get('range').getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
|
||||
var dateRange = piwikPeriods.get(piwik.period).parse(startDate).getDateRange();
|
||||
return $.datepicker.formatDate('yy-mm-dd', dateRange[0]) + ',' + $.datepicker.formatDate('yy-mm-dd', dateRange[1]);
|
||||
}
|
||||
|
||||
function isIdGoalSet() {
|
||||
return vm.idGoal || vm.idGoal === 0;
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,61 @@
|
||||
.singleMetricView {
|
||||
margin: 5px 12px 10px;
|
||||
display: inline-block;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
vertical-align: top;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.metric-sparkline {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
img {
|
||||
width: 100px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.metricEvolution {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
|
||||
&:not(.positive-evolution):not(.negative-evolution) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.positive-evolution::before {
|
||||
content: "";
|
||||
background: url(plugins/MultiSites/images/arrow_up.png) no-repeat center center;
|
||||
display: inline-block;
|
||||
height: 7px;
|
||||
width: 12px;
|
||||
}
|
||||
.negative-evolution::before {
|
||||
content: "";
|
||||
background: url(plugins/MultiSites/images/arrow_down.png) no-repeat center center;
|
||||
display: inline-block;
|
||||
height: 7px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[piwik-single-metric-view] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.single-metric-view-picker {
|
||||
margin-left: 6px;
|
||||
}
|
Reference in New Issue
Block a user