Files
stackviz/app/js/directives/timeline-viewport.js
Austin Clark 093c02d0b9 Fix lint errors and update eslint
Fixes a couple of lint errors caught by `npm run lint` in timeline-
overview.js and timeline-viewport.js. Also restructures the test-
details controller to use appropriate `this.` syntax instead of
`$scope.`

In addition, eslint and eslint-config-openstack have been updated,
and a new .eslintrc.json config file has been created to tweak some
specific rules for stackviz.

Change-Id: I9e1fe5121621730eb3efda4b99e9fe182f399aee
2016-02-04 13:52:12 -07:00

360 lines
9.8 KiB
JavaScript

'use strict';
var directivesModule = require('./_index.js');
var d3 = require('d3');
/**
* @ngInject
*/
function timelineViewport($document) {
var link = function(scope, el, attrs, timelineController) {
var margin = timelineController.margin;
var height = 200;
var loaded = false;
var y = d3.scale.linear();
var xSelected = timelineController.axes.selection;
var statusColorMap = timelineController.statusColorMap;
var chart = d3.select(el[0])
.append('svg')
.attr('width', timelineController.width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
var defs = chart.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', timelineController.width);
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var laneLines = main.append('g');
var laneLabels = main.append('g');
var itemGroups = main.append('g');
var cursorGroup = main.append('g')
.style('opacity', 0)
.style('pointer-events', 'none');
var cursor = cursorGroup.append('line')
.attr('x1', 0)
.attr('x2', 0)
.attr('stroke', 'blue');
var cursorText = cursorGroup.append('text')
.attr('x', 0)
.attr('y', -10)
.attr('dy', '-.5ex')
.style('text-anchor', 'middle')
.style('font', '9px sans-serif');
var cursorItemText = cursorGroup.append('text')
.attr('x', 0)
.attr('y', -22)
.attr('dy', '-.5ex')
.style('text-anchor', 'middle')
.style('font', '12px sans-serif')
.style('font-weight', 'bold');
var format = d3.time.format('%H:%M');
var axis = d3.svg.axis()
.scale(xSelected)
.tickSize(5)
.tickFormat(function(f) { return format(new Date(f)); })
.orient('bottom');
var axisGroup = chart.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + margin.left + ',' + (height + margin.top) + ')')
.attr('clip-path', 'url(#clip)')
.call(axis);
var selectedRect = null;
var color = function(rect, color) {
if (!rect.attr('data-old-fill')) {
rect.attr('data-old-fill', rect.attr('fill'));
}
rect.attr('fill', color);
};
var uncolor = function(rect) {
if (!$document[0].contains(rect[0][0])) {
// we lost the original colored rect so we can't unset its color,
// force a full reload
updateItems(timelineController.data);
return;
}
if (rect.attr('data-old-fill')) {
rect.attr('fill', rect.attr('data-old-fill'));
rect.attr('data-old-fill', null);
}
};
var rectMouseOver = function(d) {
timelineController.setHover(d);
scope.$apply();
if (!timelineController.selection ||
d !== timelineController.selection.item) {
color(d3.select(this), statusColorMap.hover);
}
};
var rectMouseOut = function(d) {
timelineController.clearHover();
scope.$apply();
if (!timelineController.selection ||
d !== timelineController.selection.item) {
var self = d3.select(this);
uncolor(d3.select(this));
}
};
var rectClick = function(d) {
timelineController.selectItem(d);
scope.$apply();
};
var updateLanes = function(data) {
var lines = laneLines.selectAll('.laneLine')
.data(data, function(d) { return d.key; });
lines.enter().append('line')
.attr('x1', 0)
.attr('x2', timelineController.width)
.attr('stroke', 'lightgray')
.attr('class', 'laneLine');
lines.attr('y1', function(d, i) { return y(i - 0.1); })
.attr('y2', function(d, i) { return y(i - 0.1); });
lines.exit().remove();
var labels = laneLabels.selectAll('.laneLabel')
.data(data, function(d) { return d.key; });
labels.enter().append('text')
.text(function(d) { return 'Worker #' + d.key; })
.attr('x', -margin.right)
.attr('dy', '.5ex')
.attr('text-anchor', 'end')
.attr('class', 'laneLabel');
labels.attr('y', function(d, i) { return y(i + 0.5); });
labels.exit().remove();
cursor.attr('y2', y(data.length - 0.1));
};
var updateItems = function(data) {
var extent = timelineController.viewExtents;
var minExtent = extent[0];
var maxExtent = extent[1];
// filter visible items to include only those within the current extent
// additionally prune extremely small values to improve performance
var visibleItems = data.map(function(group) {
return {
key: group.key,
values: group.values.filter(function(e) {
if (timelineController.hidden(e)) {
return false;
}
if (e.startDate > maxExtent || e.endDate < minExtent) {
return false;
}
return true;
})
};
});
var groups = itemGroups.selectAll("g")
.data(visibleItems, function(d) { return d.key; });
groups.enter().append("g");
var rects = groups.selectAll("rect")
.data(function(d) { return d.values; }, function(d) { return d.name; });
rects.enter().append("rect")
.attr('y', function(d) { return y(d.worker); })
.attr('height', 0.8 * y(1))
.attr('stroke', 'rgba(100, 100, 100, 0.25)')
.attr('clip-path', 'url(#clip)');
rects
.attr('x', function(d) {
return xSelected(d.startDate);
})
.attr('width', function(d) {
return xSelected(d.endDate) - xSelected(d.startDate);
})
.attr('fill', function(d) {
if (timelineController.selectionName === d.name) {
return statusColorMap.selected;
} else {
return statusColorMap[d.status];
}
})
.attr('data-old-fill', function(d) {
if (timelineController.selectionName === d.name) {
return statusColorMap[d.status];
} else {
return null;
}
})
.attr('class', function(d) {
if (timelineController.filterFunction) {
if (timelineController.filterFunction(d)) {
return 'filter-hit';
} else {
return 'filter-miss';
}
} else {
return null;
}
})
.on("mouseover", rectMouseOver)
.on('mouseout', rectMouseOut)
.on('click', rectClick);
rects.exit().remove();
groups.exit().remove();
};
var update = function(data) {
updateItems(timelineController.data);
updateLanes(timelineController.data);
axisGroup.call(axis);
};
var select = function(rect) {
if (selectedRect) {
uncolor(selectedRect);
}
selectedRect = rect;
if (rect !== null) {
color(rect, statusColorMap.selected);
}
};
chart.on('mouseout', function() {
cursorGroup.style('opacity', 0);
});
chart.on('mousemove', function() {
var pos = d3.mouse(this);
var px = pos[0];
var py = pos[1];
if (px >= margin.left && px < (timelineController.width + margin.left) &&
py > margin.top && py < (height + margin.top)) {
var relX = px - margin.left;
var currentTime = new Date(xSelected.invert(relX));
cursorGroup
.style('opacity', '0.5')
.attr('transform', 'translate(' + relX + ', 0)');
cursorText.text(d3.time.format('%X')(currentTime));
if (timelineController.hover) {
var name = timelineController.hover.name.split('.').pop();
cursorItemText.text(name);
var width = cursorItemText.node().getComputedTextLength();
var leftEdge = margin.left;
var rightEdge = timelineController.width + margin.left;
if (px + (width / 2) > rightEdge) {
cursorItemText.attr('dx', -(px - (rightEdge - width / 2)));
} else if (px - (width / 2) < leftEdge) {
cursorItemText.attr('dx', (leftEdge + width / 2) - px);
} else {
cursorItemText.attr('dx', 0);
}
} else {
cursorItemText.text('');
cursorItemText.attr('dx', 0);
}
}
});
scope.$on('dataLoaded', function(event, data) {
y.domain([0, data.length]).range([0, height]);
defs.attr('height', height);
cursor.attr('y1', y(-0.1));
loaded = true;
});
scope.$on('update', function() {
if (!loaded) {
return;
}
chart.attr('width', timelineController.width + margin.left + margin.right);
defs.attr('width', timelineController.width);
update(timelineController.data);
});
scope.$on('updateView', function() {
if (!loaded) {
return;
}
update(timelineController.data);
});
scope.$on('postSelect', function(event, selection) {
if (selection) {
if (timelineController.hidden(selection.item)) {
if (selectedRect) {
uncolor(selectedRect);
}
} else {
// iterate over all rects to find match
itemGroups.selectAll('rect').each(function(d) {
if (d.name === selection.item.name) {
select(d3.select(this));
}
});
}
} else {
select(null);
}
});
scope.$on('filter', function() {
if (loaded) {
update();
}
});
};
return {
restrict: 'E',
require: '^timeline',
scope: true,
link: link
};
}
directivesModule.directive('timelineViewport', timelineViewport);