From 35a5b6b9f73a24fbc18e8b08115389c4b2441f55 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Fri, 20 Nov 2015 16:31:21 -0700 Subject: [PATCH] Allow sharing of selected item by page URL. This patch adds the ability for users to share a selected timeline item via the page URL. When an item is selected, the page address is updated to include the current test name as a parameter. If this link is shared, the test named in the URL will automatically be highlighed when the page is loaded. Change-Id: I228d58e68ee986f621a3763bba1a565300c79023 --- app/js/controllers/timeline.js | 15 +++++- app/js/directives/timeline.js | 89 +++++++++++++++++++++++----------- app/js/on_config.js | 3 +- app/views/timeline.html | 3 +- 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/app/js/controllers/timeline.js b/app/js/controllers/timeline.js index 752c842..226534d 100644 --- a/app/js/controllers/timeline.js +++ b/app/js/controllers/timeline.js @@ -5,7 +5,7 @@ var controllersModule = require('./_index'); /** * @ngInject */ -function TimelineCtrl($stateParams, datasetService) { +function TimelineCtrl($scope, $location, $stateParams, datasetService) { // ViewModel var vm = this; @@ -19,6 +19,19 @@ function TimelineCtrl($stateParams, datasetService) { vm.hoveredItem = null; vm.selectedItem = null; + vm.preselect = $location.search().test; + + $scope.$watch(function() { + return vm.selectedItem; + }, function(value) { + if (value) { + $location.search({ test: value.name }); + vm.preselect = null; + } else if (vm.preselect === null) { + $location.search({ test: null }); + } + }); + } controllersModule.controller('TimelineCtrl', TimelineCtrl); diff --git a/app/js/directives/timeline.js b/app/js/directives/timeline.js index a978a7e..fc89d90 100644 --- a/app/js/directives/timeline.js +++ b/app/js/directives/timeline.js @@ -134,11 +134,11 @@ function timeline($log, datasetService) { .append('clipPath') .attr('id', 'clip') .append('rect') - .attr('width', width); // TODO: set height later + .attr('width', width); var main = chart.append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - .attr('width', width); // TODO: set height later + .attr('width', width); var laneLines = main.append('g'); var laneLabels = main.append('g'); @@ -280,31 +280,8 @@ function timeline($log, datasetService) { }) .on('click', function(d) { var self = d3.select(this); - if (selectedRect) { - if (selectedRect.attr('data-old-fill')) { - selectedRect.attr('fill', selectedRect.attr('data-old-fill')); - selectedRect.attr('data-old-fill', null); - } - - if (scope.selectedItem.name === d.name) { - scope.selectedItem = null; - scope.$apply(); - - selectedRect = null; - return; - } - } - - scope.selectedItem = d; + selectItem(self, d); scope.$apply(); - - selectedRect = self; - - if (!self.attr('data-old-fill')) { - self.attr('data-old-fill', self.attr('fill')); - } - - self.attr('fill', 'goldenrod'); }); rects.exit().remove(); @@ -371,6 +348,30 @@ function timeline($log, datasetService) { groups.exit().remove(); }; + var selectItem = function(element, datum) { + if (selectedRect) { + if (selectedRect.attr('data-old-fill')) { + selectedRect.attr('fill', selectedRect.attr('data-old-fill')); + selectedRect.attr('data-old-fill', null); + } + + if (scope.selectedItem.name === datum.name) { + scope.selectedItem = null; + selectedRect = null; + return; + } + } + + scope.selectedItem = datum; + selectedRect = element; + + if (!element.attr('data-old-fill')) { + element.attr('data-old-fill', element.attr('fill')); + } + + element.attr('fill', 'goldenrod'); + }; + var initChart = function() { // determine lanes available based on current data dstatLanes = getDstatLanes(dstat.entries, dstat.minimums, dstat.maximums); @@ -413,6 +414,7 @@ function timeline($log, datasetService) { .on('brush', update); var brushElement = mini.append('g') + .attr('class', 'brush') .call(brush) .selectAll('rect') .attr('y', 1) @@ -481,6 +483,7 @@ function timeline($log, datasetService) { // find data extents var minStart = null; var maxEnd = null; + var preselect = null; raw.forEach(function(d) { d.startDate = new Date(d.timestamps[0]); @@ -492,6 +495,10 @@ function timeline($log, datasetService) { if (maxEnd === null || d.endDate > maxEnd) { maxEnd = d.endDate; } + + if (scope.preselect && d.name === scope.preselect) { + preselect = d; + } }); // define a nested data structure with groups by worker, and fill using @@ -513,6 +520,32 @@ function timeline($log, datasetService) { timeExtents = [ minStart, maxEnd ]; initChart(); + + if (preselect) { + // determine desired viewport and item sizes to center view on + var extentSize = (maxEnd - minStart) / 8; + var itemLength = (preselect.endDate - preselect.startDate); + var itemMid = preselect.startDate.getTime() + (itemLength / 2); + + // find real begin, end values, but don't exceed min/max time extents + var begin = Math.max(minStart.getTime(), itemMid - (extentSize / 2)); + begin = Math.min(begin, maxEnd.getTime() - extentSize); + var end = begin + extentSize; + + // update the brush extent and redraw + brush.extent([ begin, end ]); + mini.select('.brush').call(brush); + + // update items to reflect the new viewport bounds + update(); + + // find + select the actual dom element + itemGroups.selectAll('rect').each(function(d) { + if (d.name === preselect.name) { + selectItem(d3.select(this), d); + } + }); + } }; chart.on('mouseout', function() { @@ -548,7 +581,6 @@ function timeline($log, datasetService) { defs.attr('width', width); main.attr('width', width); mini.attr('width', width); - // TODO: dstat? laneLines.selectAll('.laneLine').attr('x2', width); @@ -588,7 +620,8 @@ function timeline($log, datasetService) { scope: { 'dataset': '=', 'hoveredItem': '=', - 'selectedItem': '=' + 'selectedItem': '=', + 'preselect': '=' }, link: link }; diff --git a/app/js/on_config.js b/app/js/on_config.js index 8f0e020..007570c 100644 --- a/app/js/on_config.js +++ b/app/js/on_config.js @@ -15,9 +15,10 @@ function OnConfig($stateProvider, $locationProvider, $urlRouterProvider) { }); $stateProvider.state('timeline', { - url: '/timeline/{datasetId:int}', + url: '/timeline/{datasetId:int}?test', controller: 'TimelineCtrl as timeline', templateUrl: 'timeline.html', + reloadOnSearch: false, title: 'Timeline' }); diff --git a/app/views/timeline.html b/app/views/timeline.html index a28cc9a..40ce0cb 100644 --- a/app/views/timeline.html +++ b/app/views/timeline.html @@ -25,7 +25,8 @@ + selected-item="timeline.selectedItem" + preselect="timeline.preselect">