diff --git a/core/ViewDataTable.php b/core/ViewDataTable.php index 740dd9f5b55127efc264590b5ca2958effa0e91f..92da2d9e5473014990c3f3302bbb19ae7cf32404 100644 --- a/core/ViewDataTable.php +++ b/core/ViewDataTable.php @@ -147,6 +147,7 @@ class ViewDataTable $this->viewProperties['metadata'] = array(); $this->viewProperties['translations'] = array(); $this->viewProperties['filters'] = array(); + $this->viewProperties['after_data_loaded_functions'] = array(); $this->viewProperties['related_reports'] = array(); $this->viewProperties['subtable_controller_action'] = $currentControllerAction; @@ -407,6 +408,7 @@ class ViewDataTable if ($name == 'translations' || $name == 'filters' + || $name == 'after_data_loaded_functions' ) { $this->viewProperties[$name] = array_merge($this->viewProperties[$name], $value); } else if ($name == 'related_reports') { // TODO: should process after (in overrideViewProperties) @@ -1046,6 +1048,7 @@ class ViewDataTable try { $this->loadDataTableFromAPI(); $this->postDataTableLoadedFromAPI(); + $this->executeAfterDataLoadedCallbacks(); } catch (NoAccessException $e) { throw $e; } catch (\Exception $e) { @@ -1088,6 +1091,13 @@ class ViewDataTable $this->view = $view; } + private function executeAfterDataLoadedCallbacks() + { + foreach ($this->after_data_loaded_functions as $callback) { + $callback($this->dataTable, $this); + } + } + private function getFooterIconsToShow() { $result = array(); diff --git a/core/ViewDataTable/Properties.php b/core/ViewDataTable/Properties.php index 2519dd087a0c337a8055771626aeb92996eebb0b..5c5d0630fa31144fb30417d5790f462d72824230 100644 --- a/core/ViewDataTable/Properties.php +++ b/core/ViewDataTable/Properties.php @@ -187,20 +187,6 @@ class Properties */ const CUSTOM_PARAMETERS = 'custom_parameters'; - /** - * Contains the column (if any) of the values used in the Row Picker. - * - * @see self::ROW_PICKER_VISIBLE_VALUES - */ - const ROW_PICKER_VALUE_COLUMN = 'row_picker_match_rows_by'; - - /** - * Contains the list of values available for the Row Picker. - * - * @see self::ROW_PICKER_VALUE_COLUMN - */ - const ROW_PICKER_VISIBLE_VALUES = 'row_picker_visible_rows'; - /** * Whether to run generic filters on the DataTable before rendering or not. * @@ -364,6 +350,18 @@ class Properties */ const FILTERS = 'filters'; + /** + * Array of callbacks that are called after the data for a ViewDataTable is successfully + * loaded. Each callback is invoked with the DataTable instance obtained from the API + * and the ViewDatable instance that loaded it. + * + * Functions can be appended to this array property when it's necessary to configure + * a ViewDataTable after data has been loaded. If you need to use properties that are + * only set after data is loaded (like 'columns_to_display'), you'll have to use this + * property. + */ + const AFTER_DATA_LOADED_FUNCTIONS = 'after_data_loaded_functions'; + /** * Contains the controller action to call when requesting subtables of the current report. */ @@ -551,6 +549,7 @@ class Properties 'documentation' => false, 'datatable_css_class' => false, 'filters' => array(), + 'after_data_loaded_functions' => array(), 'hide_annotations_view' => true, 'columns_to_display' => array(), ); diff --git a/core/Visualization/Graph.php b/core/Visualization/Graph.php index a5953029947bcf68346958e8098d18cb3f4c252d..38da416d97cdc7d7bfc79ab9be935b72713ee43f 100644 --- a/core/Visualization/Graph.php +++ b/core/Visualization/Graph.php @@ -11,6 +11,7 @@ namespace Piwik\Visualization; use Piwik\Common; +use Piwik\DataTable\Row; use Piwik\DataTableVisualization; /** @@ -38,6 +39,30 @@ abstract class Graph extends DataTableVisualization */ const SELECTABLE_COLUMNS = 'selectable_columns'; + /** + * Contains the column (if any) of the values used in the Row Picker. + * + * @see self::ROW_PICKER_VISIBLE_VALUES + */ + const ROW_PICKER_VALUE_COLUMN = 'row_picker_match_rows_by'; + + /** + * Contains the list of values available for the Row Picker. + * TODO: row_picker_visible_rows & selectable_rows are different, but it seems like they shouldn't + * be... + * + * @see self::ROW_PICKER_VALUE_COLUMN + */ + const ROW_PICKER_VISIBLE_VALUES = 'row_picker_visible_rows'; + + /** + * Contains the list of values available for the Row Picker. Currently set to be all visible + * rows, if the row_picker_match_rows_by property is set. + * + * @see self::ROW_PICKER_VALUE_COLUMN + */ + const SELECTABLE_ROWS = 'selectable_rows'; + /** * Controls whether all ticks & labels are shown on a graph's x-axis or just some. */ @@ -64,6 +89,13 @@ abstract class Graph extends DataTableVisualization */ const DISPLAY_PERCENTAGE_IN_TOOLTIP = 'display_percentage_in_tooltip'; + public static $clientSideProperties = array( + 'show_series_picker', + 'allow_multi_select_series_picker', + 'selectable_columns', + 'selectable_rows' + ); + public static $clientSideParameters = array( 'columns' ); @@ -91,16 +123,13 @@ abstract class Graph extends DataTableVisualization if ($view->visualization_properties->max_graph_elements) { $view->request_parameters_to_modify['filter_truncate'] = $view->visualization_properties->max_graph_elements - 1; } + + $this->transformSelectableColumns($view); + $this->transformSelectableRows($view); } public static function getDefaultPropertyValues() { - // selectable columns - $selectableColumns = array('nb_visits', 'nb_actions'); - if (Common::getRequestVar('period', false) == 'day') { // TODO: should depend on columns datatable has. - $selectableColumns[] = 'nb_uniq_visitors'; - } - return array( 'visualization_properties' => array( 'graph' => array( @@ -108,11 +137,89 @@ abstract class Graph extends DataTableVisualization 'show_all_ticks' => false, 'allow_multi_select_series_picker' => true, 'max_graph_elements' => false, - 'selectable_columns' => $selectableColumns, + 'selectable_columns' => false, 'show_series_picker' => true, 'display_percentage_in_tooltip' => true, + 'row_picker_match_rows_by' => false, + 'row_picker_visible_rows' => array(), ) ) ); } + + /** + * TODO + */ + private function transformSelectableColumns($view) + { + $view->after_data_loaded_functions[] = function ($dataTable, $view) { + $selectableColumns = $view->visualization_properties->selectable_columns; + + // set default selectable columns, if none specified + if ($selectableColumns === false) { + $selectableColumns = array('nb_visits', 'nb_actions'); + + if (in_array('nb_uniq_visitors', $dataTable->getColumns())) { + $selectableColumns[] = 'nb_uniq_visitors'; + } + } + + $transformed = array(); + foreach ($selectableColumns as $column) { + $transformed[] = array( + 'column' => $column, + 'translation' => @$view->translations[$column], + 'displayed' => in_array($column, $view->columns_to_display) + ); + } + $view->visualization_properties->selectable_columns = $transformed; + }; + } + + /** + * TODO + */ + private function transformSelectableRows($view) + { + if ($view->visualization_properties->row_picker_match_rows_by === false) { + return; + } + + // collect all selectable rows + $selectableRows = array(); + $view->filters[] = function ($dataTable, $view) use (&$selectableRows) { + if ($dataTable->getRowsCount() > 0) { + $rows = $dataTable->getRows(); + } else { + $rows = array(new Row()); + } + + foreach ($rows as $row) { + $rowLabel = $row->getColumn('label'); + if ($rowLabel === false) { + continue; + } + + // determine whether row is visible + $isVisible = true; + if ($view->visualization_properties->row_picker_match_rows_by == 'label') { + $isVisible = in_array($rowLabel, $view->visualization_properties->row_picker_visible_rows); + } + + // build config + if (!isset($selectableRows[$rowLabel])) { + $selectableRows[$rowLabel] = array( + 'label' => $rowLabel, + 'matcher' => $rowLabel, + 'displayed' => $isVisible + ); + } + } + }; + + // set selectable rows as a view property + $view->after_data_loaded_functions[] = function ($dataTable, $view) use (&$selectableRows) { + $view->visualization_properties->selectable_rows = array_values($selectableRows); + }; + } } \ No newline at end of file diff --git a/plugins/CoreVisualizations/CoreVisualizations.php b/plugins/CoreVisualizations/CoreVisualizations.php index a1b970459e4152d4794c1d4ec91e23002f3eef5f..3c14a5dcaeadbb34d22c0785608ec3c3d4186363 100644 --- a/plugins/CoreVisualizations/CoreVisualizations.php +++ b/plugins/CoreVisualizations/CoreVisualizations.php @@ -53,6 +53,7 @@ class CoreVisualizations extends \Piwik\Plugin public function getJsFiles(&$jsFiles) { + $jsFiles[] = "plugins/CoreVisualizations/javascripts/seriesPicker.js"; $jsFiles[] = "plugins/CoreVisualizations/javascripts/jqplot.js"; } } \ No newline at end of file diff --git a/plugins/CoreVisualizations/JqplotDataGenerator.php b/plugins/CoreVisualizations/JqplotDataGenerator.php index 6de29e226b43867d5180eadabb827aed5373ca25..24be6f7cc51686ebe2e6d5485692f28e1fcf920f 100644 --- a/plugins/CoreVisualizations/JqplotDataGenerator.php +++ b/plugins/CoreVisualizations/JqplotDataGenerator.php @@ -137,8 +137,6 @@ class JqplotDataGenerator $units = $this->getUnitsForColumnsToDisplay(); $visualization->setAxisYUnits($units); - - $this->addSeriesPickerToView(); } protected function getUnitsForColumnsToDisplay() @@ -172,27 +170,4 @@ class JqplotDataGenerator } return $units; } - - /** - * Used in initChartObjectData to add the series picker config to the view object - */ - protected function addSeriesPickerToView() - { - $defaultShowSeriesPicker = $this->properties['visualization_properties']->show_series_picker; - if (count($this->properties['visualization_properties']->selectable_columns) - && Common::getRequestVar('showSeriesPicker', $defaultShowSeriesPicker) == 1 - ) { - $selectableColumns = array(); - foreach ($this->properties['visualization_properties']->selectable_columns as $column) { - $selectableColumns[] = array( - 'column' => $column, - 'translation' => @$this->properties['translations'][$column], - 'displayed' => in_array($column, $this->properties['columns_to_display']) - ); - } - - $this->visualization->setSelectableColumns( - $selectableColumns, $this->properties['visualization_properties']->allow_multi_select_series_picker); - } - } -} +} \ No newline at end of file diff --git a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php index 05ff6ab3b3029b0dc7a6957bb58dfa9a0c7c479c..d54bb618244a5fb9f8096d0753d8f7c1c61e3b53 100644 --- a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php +++ b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php @@ -64,14 +64,11 @@ class Evolution extends JqplotDataGenerator foreach ($rows as $row) { $rowLabel = $row->getColumn('label'); - // put together configuration for row picker. - // do this for every data table in the array because rows do not - // have to present for each date. - if ($this->properties['row_picker_match_rows_by'] !== false) { - $rowVisible = $this->handleRowForRowPicker($rowLabel); - if (!$rowVisible) { - continue; - } + // only process 'visible' rows (visibility is determined by row_picker_visible_rows property) + if ($this->properties['visualization_properties']->row_picker_match_rows_by == 'label' + && !in_array($rowLabel, $this->properties['visualization_properties']->row_picker_visible_rows) + ) { + continue; } // build data for request columns @@ -138,40 +135,6 @@ class Evolution extends JqplotDataGenerator } $visualization->setAxisXOnClick($axisXOnClick); } - - $this->addSeriesPickerToView(); - - // configure the row picker - if ($this->properties['row_picker_match_rows_by'] !== false) { - $visualization->setSelectableRows(array_values($this->rowPickerConfig)); - } - } - - /** - * This method is called for every row of every table in the DataTable_Array. - * It incrementally builds the row picker configuration and determines whether - * the row is initially visible or not. - * @param string $rowLabel - * @return bool - */ - private function handleRowForRowPicker(&$rowLabel) - { - // determine whether row is visible - $isVisible = true; - if ($this->properties['row_picker_match_rows_by'] == 'label') { - $isVisible = in_array($rowLabel, $this->properties['row_picker_visible_rows']); - } - - // build config - if (!isset($this->rowPickerConfig[$rowLabel])) { - $this->rowPickerConfig[$rowLabel] = array( - 'label' => $rowLabel, - 'matcher' => $rowLabel, - 'displayed' => $isVisible - ); - } - - return $isVisible; } /** diff --git a/plugins/CoreVisualizations/Visualizations/JqplotGraph.php b/plugins/CoreVisualizations/Visualizations/JqplotGraph.php index 71a9ef9b2c84c08042c4e9f323725f9a6aee450a..666b9a510f04077f9ca25536c240b0db5e039b5c 100644 --- a/plugins/CoreVisualizations/Visualizations/JqplotGraph.php +++ b/plugins/CoreVisualizations/Visualizations/JqplotGraph.php @@ -77,8 +77,6 @@ class JqplotGraph extends Graph 'show_search' => false, 'show_export_as_image_icon' => true, 'y_axis_unit' => '', - 'row_picker_match_rows_by' => false, - 'row_picker_visible_rows' => array(), 'visualization_properties' => array( 'jqplot_graph' => array( 'external_series_toggle' => false, diff --git a/plugins/CoreVisualizations/javascripts/jqplot.js b/plugins/CoreVisualizations/javascripts/jqplot.js index 499e14bfe16d68a8cc68e4653c228f7bce4e9779..a521f76563385023ce3580731c3c440c0ae617c6 100644 --- a/plugins/CoreVisualizations/javascripts/jqplot.js +++ b/plugins/CoreVisualizations/javascripts/jqplot.js @@ -331,7 +331,6 @@ JQPlot.prototype = { prepareEvolutionChart: function (targetDivId, lang) { this.setYTicks(); - this.addSeriesPicker(targetDivId, lang); defaultParams.axes = { xaxis: { @@ -419,8 +418,6 @@ JQPlot.prototype = { // ------------------------------------------------------------ preparePieChart: function (targetDivId, lang) { - this.addSeriesPicker(targetDivId, lang); - this.params.seriesDefaults = { renderer: $.jqplot.PieRenderer, rendererOptions: { @@ -465,7 +462,6 @@ JQPlot.prototype = { prepareBarChart: function (targetDivId, lang) { this.setYTicks(); - this.addSeriesPicker(targetDivId, lang); this.params.seriesDefaults = { renderer: $.jqplot.BarRenderer, @@ -567,19 +563,6 @@ JQPlot.prototype = { return value; }, - addSeriesPicker: function (targetDivId, lang) { - this.params.seriesPicker = { - show: typeof this.seriesPicker.selectableColumns == 'object' - || typeof this.seriesPicker.selectableRows == 'object', - selectableColumns: this.seriesPicker.selectableColumns, - selectableRows: this.seriesPicker.selectableRows, - multiSelect: this.seriesPicker.multiSelect, - targetDivId: targetDivId, - dataTableId: this.dataTableId, - lang: lang - }; - }, - /** * Add an external series toggle. * As opposed to addSeriesPicker, the external series toggle can only show/hide @@ -1051,248 +1034,28 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function () { // ------------------------------------------------------------ // SERIES PICKER -// For line charts // ------------------------------------------------------------ (function ($) { - - $.jqplot.SeriesPicker = function (options) { - // dom element - this.domElem = null; - // render the picker? - this.show = false; - // the columns that can be selected - this.selectableColumns = null; - // the rows that can be selected - this.selectableRows = null; - // can multiple rows we selected? - this.multiSelect = true; - // css id of the target div dom element - this.targetDivId = ""; - // the id of the current data table - this.dataTableId = ""; - // language strings - this.lang = {}; - - $.extend(true, this, options); - }; - - $.jqplot.SeriesPicker.init = function (target, data, opts) { + $.jqplot.preInitHooks.push(function (target, data, options) { // add plugin as an attribute to the plot - var options = opts || {}; - this.plugins.seriesPicker = new $.jqplot.SeriesPicker(options.seriesPicker); - }; + var dataTable = $('#' + target).closest('.dataTable').data('dataTableInstance'); + var seriesPicker = new piwik.SeriesPicker(dataTable); - // render the link to add series - $.jqplot.SeriesPicker.postDraw = function () { var plot = this; - var picker = plot.plugins.seriesPicker; - - if (!picker.show) { - return; - } - - // initialize dom element - picker.domElem = $(document.createElement('a')) - .addClass('jqplot-seriespicker') - .attr('href', '#').html('+') - .css('marginLeft', (plot._gridPadding.left + plot.plugins.canvasLegend.width - 1) + 'px'); - - picker.domElem.on('hide',function () { - $(this).css('opacity', .55); - }).trigger('hide'); - - plot.baseCanvas._elem.before(picker.domElem); - - // show picker on hover - picker.domElem.hover(function () { - picker.domElem.css('opacity', 1); - if (!picker.domElem.hasClass('open')) { - picker.domElem.addClass('open'); - showPicker(picker, plot._width); - } - },function () { - // do nothing on mouseout because using this event doesn't work properly. - // instead, the timeout check beneath is used (checkPickerLeave()). - }).click(function () { - return false; - }); - }; - - // show the series picker - function showPicker(picker, plotWidth) { - var pickerLink = picker.domElem; - var pickerPopover = $(document.createElement('div')) - .addClass('jqplock-seriespicker-popover'); - - var pickerState = {manipulated: false}; - - // headline - var title = picker.multiSelect ? picker.lang.metricsToPlot : picker.lang.metricToPlot; - pickerPopover.append($(document.createElement('p')) - .addClass('headline').html(title)); - - if (picker.selectableColumns !== null) { - // render the selectable columns - for (var i = 0; i < picker.selectableColumns.length; i++) { - var column = picker.selectableColumns[i]; - pickerPopover.append(createPickerPopupItem(picker, column, 'column', pickerState, pickerPopover, pickerLink)); - } - } - - if (picker.selectableRows !== null) { - // "records to plot" subheadline - pickerPopover.append($(document.createElement('p')) - .addClass('headline').addClass('recordsToPlot') - .html(picker.lang.recordsToPlot)); - - // render the selectable rows - for (var i = 0; i < picker.selectableRows.length; i++) { - var row = picker.selectableRows[i]; - pickerPopover.append(createPickerPopupItem(picker, row, 'row', pickerState, pickerPopover, pickerLink)); - } - } - - $('body').prepend(pickerPopover.hide()); - var neededSpace = pickerPopover.outerWidth() + 10; - - // try to display popover to the right - var linkOffset = pickerLink.offset(); - if (navigator.appVersion.indexOf("MSIE 7.") != -1) { - linkOffset.left -= 10; - } - var margin = (parseInt(pickerLink.css('marginLeft'), 10) - 4); - if (margin + neededSpace < plotWidth - // make sure it's not too far to the left - || margin - neededSpace + 60 < 0) { - pickerPopover.css('marginLeft', (linkOffset.left - 4) + 'px').show(); - } else { - // display to the left - pickerPopover.addClass('alignright') - .css('marginLeft', (linkOffset.left - neededSpace + 38) + 'px') - .css('backgroundPosition', (pickerPopover.outerWidth() - 25) + 'px 4px') - .show(); - } - pickerPopover.css('marginTop', (linkOffset.top - 5) + 'px').show(); - - // hide and replot on mouse leave - checkPickerLeave(pickerPopover, function () { - var replot = pickerState.manipulated; - hidePicker(picker, pickerPopover, pickerLink, replot); - }); - } - - function createPickerPopupItem(picker, config, type, pickerState, pickerPopover, pickerLink) { - var checkbox = $(document.createElement('input')).addClass('select') - .attr('type', picker.multiSelect ? 'checkbox' : 'radio'); - - if (config.displayed && !(!picker.multiSelect && pickerState.oneChecked)) { - checkbox.prop('checked', true); - pickerState.oneChecked = true; - } - - // if we are rendering a column, remember the column name - // if it's a row, remember the string that can be used to match the row - checkbox.data('name', type == 'column' ? config.column : config.matcher); - - var el = $(document.createElement('p')) - .append(checkbox) - .append('<label>' + (type == 'column' ? config.translation : config.label) + '</label>') - .addClass(type == 'column' ? 'pickColumn' : 'pickRow'); - - var replot = function () { - unbindPickerLeaveCheck(); - hidePicker(picker, pickerPopover, pickerLink, true); - }; - - var checkBox = function (box) { - if (!picker.multiSelect) { - pickerPopover.find('input.select:not(.current)').prop('checked', false); - } - box.prop('checked', true); - replot(); - }; - - el.click(function (e) { - pickerState.manipulated = true; - var box = $(this).find('input.select'); - if (!$(e.target).is('input.select')) { - if (box.is(':checked')) { - box.prop('checked', false); - } else { - checkBox(box); - } - } else { - if (box.is(':checked')) { - checkBox(box); - } - } - }); - - return el; - } - - // check whether the mouse has left the picker - var onMouseMove; - - function checkPickerLeave(pickerPopover, onLeaveCallback) { - var offset = pickerPopover.offset(); - var minX = offset.left; - var minY = offset.top; - var maxX = minX + pickerPopover.outerWidth(); - var maxY = minY + pickerPopover.outerHeight(); - var currentX, currentY; - onMouseMove = function (e) { - currentX = e.pageX; - currentY = e.pageY; - if (currentX < minX || currentX > maxX - || currentY < minY || currentY > maxY) { - unbindPickerLeaveCheck(); - onLeaveCallback(); - } - }; - $(document).mousemove(onMouseMove); - } - - function unbindPickerLeaveCheck() { - $(document).unbind('mousemove', onMouseMove); - } - - function hidePicker(picker, pickerPopover, pickerLink, replot) { - // hide picker - pickerPopover.hide(); - pickerLink.trigger('hide').removeClass('open'); - - // replot - if (replot) { - var columns = []; - var rows = []; - pickerPopover.find('input:checked').each(function () { - if ($(this).closest('p').hasClass('pickRow')) { - rows.push($(this).data('name')); - } else { - columns.push($(this).data('name')); - } - }); - var noRowSelected = pickerPopover.find('.pickRow').size() > 0 - && pickerPopover.find('.pickRow input:checked').size() == 0; - if (columns.length > 0 && !noRowSelected) { - - $('#' + picker.targetDivId).trigger('changeSeries', [columns, rows]); - // inform dashboard widget about changed parameters (to be restored on reload) - $('#' + picker.targetDivId).parents('[widgetId]').trigger('setParameters', {columns: columns, rows: rows}); - } - } - - pickerPopover.remove(); - } + $(seriesPicker).bind('placeSeriesPicker', function () { + this.domElem.css('margin-left', (plot._gridPadding.left + plot.plugins.canvasLegend.width - 1) + 'px'); + plot.baseCanvas._elem.before(this.domElem); + }) - $.jqplot.preInitHooks.push($.jqplot.SeriesPicker.init); - $.jqplot.postDrawHooks.push($.jqplot.SeriesPicker.postDraw); + this.plugins.seriesPicker = seriesPicker; + }); + $.jqplot.postDrawHooks.push(function () { + this.plugins.seriesPicker.createElement(this); + }); })(jQuery); - // ------------------------------------------------------------ // PIE CHART LEGEND PLUGIN FOR JQPLOT // Render legend inside the pie graph diff --git a/plugins/CoreVisualizations/javascripts/seriesPicker.js b/plugins/CoreVisualizations/javascripts/seriesPicker.js new file mode 100644 index 0000000000000000000000000000000000000000..97e93a69f7f759139b3b0d535f0294c37b34584d --- /dev/null +++ b/plugins/CoreVisualizations/javascripts/seriesPicker.js @@ -0,0 +1,273 @@ +/** + * Piwik - Web Analytics + * + * Adapter for jqplot + * + * @link http://www.jqplot.com + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +(function ($) { + + /** + * This class creates and manages the Series Picker for certain DataTable visualizations. + * + * @param {dataTable} The dataTable instance to add a series picker to. + */ + var SeriesPicker = function (dataTable) { + + // dom element + this.domElem = null; + + // the columns that can be selected + this.selectableColumns = dataTable.props.selectable_columns; + + // the rows that can be selected + this.selectableRows = dataTable.props.selectable_rows; + + // render the picker? + this.show = !! dataTable.props.show_series_picker + && (this.selectableColumns || this.selectableRows); + + // can multiple rows we selected? + this.multiSelect = !! dataTable.props.allow_multi_select_series_picker; + + this.dataTableId = dataTable.workingDivId; + + // language strings + this.lang = + { + metricsToPlot: _pk_translate('General_MetricsToPlot_js'), + metricToPlot: _pk_translate('General_MetricToPlot_js'), + recordsToPlot: _pk_translate('General_RecordsToPlot_js') + }; + }; + + SeriesPicker.prototype = { + + /** + * TODO + */ + createElement: function (plot) { + if (!this.show) { + return; + } + + // initialize dom element + this.domElem = $(document.createElement('a')) + .addClass('jqplot-seriespicker') + .attr('href', '#').html('+'); + + this.domElem.on('hide', function () { + $(this).css('opacity', .55); + }).trigger('hide'); + + // show picker on hover + var self = this; + this.domElem.hover( + function () { + self.domElem.css('opacity', 1); + if (!self.domElem.hasClass('open')) { + self.domElem.addClass('open'); + self.showPicker(plot._width); // TODO: ??? + } + }, + function () { + // do nothing on mouseout because using this event doesn't work properly. + // instead, the timeout check beneath is used (checkPickerLeave()). + } + ).click(function (e) { + e.preventDefault(); + return false; + }); + + $(this).trigger('placeSeriesPicker'); // TODO: document this & other events + }, + + /** + * TODO + */ + showPicker: function (plotWidth) { + var pickerLink = this.domElem; + var pickerPopover = $(document.createElement('div')) + .addClass('jqplot-seriespicker-popover'); + + var pickerState = {manipulated: false}; + + // headline + var title = this.multiSelect ? this.lang.metricsToPlot : this.lang.metricToPlot; + pickerPopover.append($(document.createElement('p')) + .addClass('headline').html(title)); + + if (this.selectableColumns) { + // render the selectable columns + for (var i = 0; i < this.selectableColumns.length; i++) { + var column = this.selectableColumns[i]; + pickerPopover.append(this.createPickerPopupItem(column, 'column', pickerState, pickerPopover, pickerLink)); + } + } + + if (this.selectableRows) { + // "records to plot" subheadline + pickerPopover.append($(document.createElement('p')) + .addClass('headline').addClass('recordsToPlot') + .html(this.lang.recordsToPlot)); + + // render the selectable rows + for (var i = 0; i < this.selectableRows.length; i++) { + var row = this.selectableRows[i]; + pickerPopover.append(this.createPickerPopupItem(row, 'row', pickerState, pickerPopover, pickerLink)); + } + } + + $('body').prepend(pickerPopover.hide()); + var neededSpace = pickerPopover.outerWidth() + 10; + + // try to display popover to the right + var linkOffset = pickerLink.offset(); + if (navigator.appVersion.indexOf("MSIE 7.") != -1) { + linkOffset.left -= 10; + } + var margin = (parseInt(pickerLink.css('marginLeft'), 10) - 4); + if (margin + neededSpace < plotWidth + // make sure it's not too far to the left + || margin - neededSpace + 60 < 0) { + pickerPopover.css('marginLeft', (linkOffset.left - 4) + 'px').show(); + } else { + // display to the left + pickerPopover.addClass('alignright') + .css('marginLeft', (linkOffset.left - neededSpace + 38) + 'px') + .css('backgroundPosition', (pickerPopover.outerWidth() - 25) + 'px 4px') + .show(); + } + pickerPopover.css('marginTop', (linkOffset.top - 5) + 'px').show(); + + // hide and replot on mouse leave + var self = this; + this.checkPickerLeave(pickerPopover, function () { + var replot = pickerState.manipulated; + self.hidePicker(pickerPopover, pickerLink, replot); + }); + }, + + /** + * TODO + */ + createPickerPopupItem: function (config, type, pickerState, pickerPopover, pickerLink) { + var self = this; + var checkbox = $(document.createElement('input')).addClass('select') + .attr('type', this.multiSelect ? 'checkbox' : 'radio'); + + if (config.displayed && !(!this.multiSelect && pickerState.oneChecked)) { + checkbox.prop('checked', true); + pickerState.oneChecked = true; + } + + // if we are rendering a column, remember the column name + // if it's a row, remember the string that can be used to match the row + checkbox.data('name', type == 'column' ? config.column : config.matcher); + + var el = $(document.createElement('p')) + .append(checkbox) + .append('<label>' + (type == 'column' ? config.translation : config.label) + '</label>') + .addClass(type == 'column' ? 'pickColumn' : 'pickRow'); + + var replot = function () { + self.unbindPickerLeaveCheck(); + self.hidePicker(pickerPopover, pickerLink, true); + }; + + var checkBox = function (box) { + if (!self.multiSelect) { + pickerPopover.find('input.select:not(.current)').prop('checked', false); + } + box.prop('checked', true); + replot(); + }; + + el.click(function (e) { + pickerState.manipulated = true; + var box = $(this).find('input.select'); + if (!$(e.target).is('input.select')) { + if (box.is(':checked')) { + box.prop('checked', false); + } else { + checkBox(box); + } + } else { + if (box.is(':checked')) { + checkBox(box); + } + } + }); + + return el; + }, + + /** + * TODO + */ + checkPickerLeave: function (pickerPopover, onLeaveCallback) { + var offset = pickerPopover.offset(); + var minX = offset.left; + var minY = offset.top; + var maxX = minX + pickerPopover.outerWidth(); + var maxY = minY + pickerPopover.outerHeight(); + var currentX, currentY; + var self = this; + this.onMouseMove = function (e) { + currentX = e.pageX; + currentY = e.pageY; + if (currentX < minX || currentX > maxX + || currentY < minY || currentY > maxY) { + self.unbindPickerLeaveCheck(); + onLeaveCallback(); + } + }; + $(document).mousemove(this.onMouseMove); + }, + + /** + * TODO + */ + unbindPickerLeaveCheck: function () { + $(document).unbind('mousemove', this.onMouseMove); + }, + + /** + * TODO + */ + hidePicker: function (pickerPopover, pickerLink, replot) { + // hide picker + pickerPopover.hide(); + pickerLink.trigger('hide').removeClass('open'); + + // replot + if (replot) { + var columns = []; + var rows = []; + pickerPopover.find('input:checked').each(function () { + if ($(this).closest('p').hasClass('pickRow')) { + rows.push($(this).data('name')); + } else { + columns.push($(this).data('name')); + } + }); + var noRowSelected = pickerPopover.find('.pickRow').size() > 0 + && pickerPopover.find('.pickRow input:checked').size() == 0; + if (columns.length > 0 && !noRowSelected) { + + $('#' + this.dataTableId + ' .piwik-graph').trigger('changeSeries', [columns, rows]); + // inform dashboard widget about changed parameters (to be restored on reload) + $('#' + this.dataTableId + ' .piwik-graph').parents('[widgetId]').trigger('setParameters', {columns: columns, rows: rows}); + } + } + + pickerPopover.remove(); + } + }; + + piwik.SeriesPicker = SeriesPicker; + +})(jQuery); \ No newline at end of file diff --git a/plugins/CoreVisualizations/stylesheets/jqplot.css b/plugins/CoreVisualizations/stylesheets/jqplot.css index 5a55ba760d7df7f15f7c3ef8fd459201e4d04f23..c0c6b3e0efd569c300df0193979aa948d20d169c 100644 --- a/plugins/CoreVisualizations/stylesheets/jqplot.css +++ b/plugins/CoreVisualizations/stylesheets/jqplot.css @@ -211,7 +211,7 @@ a.rowevolution-startmulti:hover { text-indent: -999px; } -.jqplock-seriespicker-popover { +.jqplot-seriespicker-popover { display: block; position: absolute; z-index: 1010; /* must be above ui dialog */ @@ -227,33 +227,33 @@ a.rowevolution-startmulti:hover { box-shadow: 1px 1px 2px #666; } -.jqplock-seriespicker-popover p { +.jqplot-seriespicker-popover p { margin: 0; padding: 0 4px 0 0; line-height: 15px; vertical-align: middle; } -.jqplock-seriespicker-popover p.headline { +.jqplot-seriespicker-popover p.headline { font-weight: bold; font-size: 12px; padding: 0 0 6px 22px; color: #7E7363; } -.jqplock-seriespicker-popover p.headline.recordsToPlot { +.jqplot-seriespicker-popover p.headline.recordsToPlot { padding: 8px 0 3px 0; } -.jqplock-seriespicker-popover.alignright p.headline { +.jqplot-seriespicker-popover.alignright p.headline { padding: 0 22px 6px 0; } -.jqplock-seriespicker-popover input.select { +.jqplot-seriespicker-popover input.select { margin-right: 8px; } -.jqplock-seriespicker-popover p.pickColumn, -.jqplock-seriespicker-popover p.pickRow { +.jqplot-seriespicker-popover p.pickColumn, +.jqplot-seriespicker-popover p.pickRow { cursor: pointer; } \ No newline at end of file diff --git a/plugins/Dashboard/stylesheets/standalone.css b/plugins/Dashboard/stylesheets/standalone.css index cc16f156d8a5916409ce93fbc276380c63012b8b..aaa1df8b9fcaeb159b9ae7f18c4c3647769bbb38 100644 --- a/plugins/Dashboard/stylesheets/standalone.css +++ b/plugins/Dashboard/stylesheets/standalone.css @@ -67,7 +67,7 @@ body { margin-right: 10px; } -.jqplock-seriespicker-popover { +.jqplot-seriespicker-popover { top: 0; } diff --git a/plugins/PleineLune/stylesheets/_dataTable.less b/plugins/PleineLune/stylesheets/_dataTable.less index 26c4393a25e0dffc4ec54a27e0dc24f181e38ce4..69a69ac70962c3b209e59a8310b10343773945af 100644 --- a/plugins/PleineLune/stylesheets/_dataTable.less +++ b/plugins/PleineLune/stylesheets/_dataTable.less @@ -1,3 +1,7 @@ +div.dataTable { + position:relative; +} + table.dataTable { border-collapse: collapse; } diff --git a/plugins/PleineLune/stylesheets/_jqplot.less b/plugins/PleineLune/stylesheets/_jqplot.less index 14f4ec053ade8cbfce7cf99c47719bb8ae39c75a..0da3075e40888a8dd5a8d0fb19db0439f3980c09 100644 --- a/plugins/PleineLune/stylesheets/_jqplot.less +++ b/plugins/PleineLune/stylesheets/_jqplot.less @@ -1,18 +1,18 @@ -.jqplock-seriespicker-popover { +.jqplot-seriespicker-popover { background-color: @theme-color-background-base; border: 1px solid @theme-color-background-contrast; border-radius: 0; color: @theme-color-text-link; } -.jqplock-seriespicker-popover > p:hover > label { +.jqplot-seriespicker-popover > p:hover > label { color: @theme-color-text-focus; } -.jqplock-seriespicker-popover :checked + label { +.jqplot-seriespicker-popover :checked + label { color: @theme-color-text-active; } -.jqplock-seriespicker-popover p.headline { +.jqplot-seriespicker-popover p.headline { color: @theme-color-text-active; } \ No newline at end of file diff --git a/plugins/Referers/Controller.php b/plugins/Referers/Controller.php index 7aea4328044c3532971f3dfa101481b715333f23..407437ac86f6e81107dfb34891de6d685ee06bf4 100644 --- a/plugins/Referers/Controller.php +++ b/plugins/Referers/Controller.php @@ -300,8 +300,8 @@ class Controller extends \Piwik\Controller $visibleRows = array($label, $total); $view->request_parameters_to_modify['rows'] = $label . ',' . $total; } - $view->row_picker_match_rows_by = 'label'; - $view->row_picker_visible_rows = $visibleRows; + $view->visualization_properties->row_picker_match_rows_by = 'label'; + $view->visualization_properties->row_picker_visible_rows = $visibleRows; $view->documentation = Piwik_Translate('Referers_EvolutionDocumentation') . '<br />' . Piwik_Translate('General_BrokenDownReportDocumentation') . '<br />' diff --git a/plugins/Zeitgeist/stylesheets/ieonly.css b/plugins/Zeitgeist/stylesheets/ieonly.css index 92bdda7e34e5dba7003e939322300a1e756555d5..bb2f7e71f1ad4dc4f126b37ab9c8cadfc33cb696 100644 --- a/plugins/Zeitgeist/stylesheets/ieonly.css +++ b/plugins/Zeitgeist/stylesheets/ieonly.css @@ -35,7 +35,7 @@ input[type=checkbox], input[type=radio] { background: url(../images/chart_line_edit_bg.png) no-repeat center center; } -.jqplock-seriespicker-popover p input { +.jqplot-seriespicker-popover p input { width: 15px; margin: 0; padding: 0;