Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synchronized Time Marker #4930

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions src/plugins/kbn_vislib_vis_types/public/area.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ define(function (require) {
mode: 'stacked',
times: [],
addTimeMarker: false,
syncTimeMarker: false,
defaultYExtents: false,
setYExtents: false,
yAxis: {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
Current time marker
</label>
</div>
<div class="vis-option-item" ng-show="vis.hasSchemaAgg('segment', 'date_histogram')">
<label>
<input type="checkbox" ng-model="vis.params.syncTimeMarker" ng-checked="vis.params.syncTimeMarker">
Synchronized Time Marker
</label>
</div>
<div class="vis-option-item">
<label>
<input type="checkbox" ng-model="vis.params.setYExtents">
Expand Down
1 change: 1 addition & 0 deletions src/plugins/kbn_vislib_vis_types/public/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ define(function (require) {
mode: 'stacked',
times: [],
addTimeMarker: false,
syncTimeMarker: false,
defaultYExtents: false,
setYExtents: false,
yAxis: {}
Expand Down
1 change: 1 addition & 0 deletions src/plugins/kbn_vislib_vis_types/public/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ define(function (require) {
radiusRatio: 9,
times: [],
addTimeMarker: false,
syncTimeMarker: false,
defaultYExtents: false,
setYExtents: false,
yAxis: {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
define(function (require) {
return function visualizationLoader(savedVisualizations, Private) { // Inject services here
return function visualizationLoader(savedVisualizations, markerSync, Private) { // Inject services here
var brushEvent = Private(require('ui/utils/brush_event'));
var filterBarClickHandler = Private(require('ui/filter_bar/filter_bar_click_handler'));

Expand All @@ -9,6 +9,7 @@ define(function (require) {
// $scope.state comes via $scope inheritence from the dashboard app. Don't love this.
savedVis.vis.listeners.click = filterBarClickHandler($scope.state);
savedVis.vis.listeners.brush = brushEvent;
savedVis.vis.listeners.hover = markerSync.hoverHandler();

return {
savedObj: savedVis,
Expand Down
231 changes: 231 additions & 0 deletions src/ui/public/vislib/__tests__/lib/marker_renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
var d3 = require('d3');
var angular = require('angular');
var expect = require('expect.js');
var sinon = require('sinon');
var moment = require('moment');
var _ = require('lodash');
var $ = require('jquery');

describe('Vislib Marker Renderer Test Suite', function () {
var markerRenderer = require('ui/vislib/lib/marker_renderer');
var renderer;
var markers;
var partial = {
'class': 'partially-customized-marker',
opacity: 0.67
};
var full = {
'class': 'fully-customized-marker',
layer: 'fully-customized-marker-layer',
color: 'blue',
opacity: 0.45,
width: 3
};

describe('module structure', function () {
it('should be an object that has 3 props', function () {
expect(_.isObject(markerRenderer)).to.be(true);
expect(_.keys(markerRenderer)).to.have.length(3);
expect(typeof markerRenderer.render).to.be('function');
expect(typeof markerRenderer.configure).to.be('function');
expect(_.isObject(markerRenderer.opts)).to.be(true);
});
});

describe('configure method', function () {
it('should return default renderer', function () {
renderer = markerRenderer.configure();
expect(_.keys(renderer.opts)).to.have.length(5);
expect(renderer.opts).to.eql(markerRenderer.opts);
});

it('should return partially configured renderer', function () {
renderer = markerRenderer.configure(partial);
expect(_.keys(renderer.opts)).to.have.length(5);
expect(renderer.opts).to.eql({
'class': 'partially-customized-marker',
layer: 'time-marker-layer',
color: '#aaa',
opacity: 0.67,
width: 1
});
});

it('should return fully configured renderer', function () {
renderer = markerRenderer.configure(full);
expect(_.keys(renderer.opts)).to.have.length(5);
expect(renderer.opts).to.eql(full);
});

it('should inherit properties from base renderer', function () {
renderer = markerRenderer.configure(partial);
renderer = renderer.configure({
'class': 'inherit-customized-marker'
});
expect(_.keys(renderer.opts)).to.have.length(5);
expect(renderer.opts).to.eql({
'class': 'inherit-customized-marker',
layer: 'time-marker-layer',
color: '#aaa',
opacity: 0.67,
width: 1
});

renderer = renderer.configure({
color: 'orange',
layer: 'orange-time-marker-layer',
opacity: 0.4567
});
expect(_.keys(renderer.opts)).to.have.length(5);
expect(renderer.opts).to.eql({
'class': 'inherit-customized-marker',
layer: 'orange-time-marker-layer',
color: 'orange',
opacity: 0.4567,
width: 1
});
});
});

describe('render method', function () {
var HEIGHT = 50;
var selection;
var xScale;
var clock;

beforeEach(function () {
clock = sinon.useFakeTimers();

var domain = [+moment().subtract(30, 'm'), +moment().add(30, 'm')];
xScale = d3.time.scale().domain(domain).range([0, 600]);

selection = d3.select('body').append('div').attr('class', 'marker');
});

afterEach(function () {
clock.restore();

selection.remove('*');
selection = null;
});

it('should render current time', function () {
markerRenderer.render(selection, xScale, HEIGHT);

markers = $('.time-marker-layer line.time-marker').get();
expect(markers.length).to.be(1);

var marker = markers[0];
expect(marker.getAttribute('x1')).to.be('300');
expect(marker.getAttribute('x2')).to.be('300');
});

it('should render specified times (UNIX timestamp)', function () {
var times = [+moment().subtract(10, 'm'), +moment().add(5, 'm'), +moment().add(25, 'm')];
markerRenderer.render(selection, xScale, HEIGHT, times);

markers = $('.time-marker-layer line.time-marker').get();
expect(markers.length).to.be(3);

var pairs = _.map(markers, function (marker) {
return [marker.getAttribute('x1'), marker.getAttribute('x2')];
});
expect(_.sortBy(_.flatten(pairs))).to.eql([200, 200, 350, 350, 550, 550]);
});

it('should render specified times (d3 data series)', function () {
var times = [+moment().subtract(5, 'm'), +moment().add(5, 'm'), +moment().add(15, 'm')];
times = times.map(function (time) {
return { class: 'd3-time-marker', time: time };
});
markerRenderer.render(selection, xScale, HEIGHT, times);

markers = _.sortBy($('.time-marker-layer line.d3-time-marker').get(), 'time');
expect(markers.length).to.be(3);

var pairs = _.map(markers, function (marker) {
return [marker.getAttribute('x1'), marker.getAttribute('x2')];
});
expect(_.sortBy(_.flatten(pairs))).to.eql([250, 250, 350, 350, 450, 450]);
});

it('should overwrite base properties with d3 data series', function () {
renderer = markerRenderer.configure({
class: 'overwrite-time-marker',
width: 1.5
});

var times = [{
time: +moment(),
class: 'red-time-marker',
color: 'red'
}, {
time: +moment().add(10, 'm'),
class: 'green-time-marker',
color: 'green',
opacity: 0.75
}, {
time: +moment().add(20, 'm'),
class: 'blue-time-marker',
color: 'blue',
width: 1
}];
renderer.render(selection, xScale, HEIGHT, times);

markers = $('.time-marker-layer line.overwrite-time-marker').get();
expect(markers.length).to.be(0);

markers = $('.time-marker-layer line.red-time-marker').get();
expect(markers.length).to.be(1);

var red = markers[0];
expect(red.getAttribute('stroke')).to.be('red');
expect(red.getAttribute('stroke-width')).to.be('1.5');
expect(red.getAttribute('stroke-opacity')).to.be('0.8');

markers = $('.time-marker-layer line.green-time-marker').get();
expect(markers.length).to.be(1);

var green = markers[0];
expect(green.getAttribute('stroke')).to.be('green');
expect(green.getAttribute('stroke-width')).to.be('1.5');
expect(green.getAttribute('stroke-opacity')).to.be('0.75');

markers = $('.time-marker-layer line.blue-time-marker').get();
expect(markers.length).to.be(1);

var blue = markers[0];
expect(blue.getAttribute('stroke')).to.be('blue');
expect(blue.getAttribute('stroke-width')).to.be('1');
expect(blue.getAttribute('stroke-opacity')).to.be('0.8');
});

it('should render markers in separated layers', function () {
var times = [+moment()];

var red = markerRenderer.configure({
layer: 'red-time-marker-layer'
});
red.render(selection, xScale, HEIGHT, times);

var green = markerRenderer.configure({
layer: 'green-time-marker-layer'
});
green.render(selection, xScale, HEIGHT, times);

var blue = markerRenderer.configure({
layer: 'blue-time-marker-layer'
});
blue.render(selection, xScale, HEIGHT, times);

markers = $('line.time-marker').get();
expect(markers.length).to.be(3);
markers = $('.red-time-marker-layer line.time-marker').get();
expect(markers.length).to.be(1);
markers = $('.green-time-marker-layer line.time-marker').get();
expect(markers.length).to.be(1);
markers = $('.blue-time-marker-layer line.time-marker').get();
expect(markers.length).to.be(1);
});
});
});
38 changes: 38 additions & 0 deletions src/ui/public/vislib/__tests__/lib/marker_synchronizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
describe('Vislib Marker Synchronizer', function () {
var _ = require('lodash');
var ngMock = require('ngMock');
var expect = require('expect.js');

var SimpleEmitter = require('ui/utils/SimpleEmitter');
var sync;

var EVENT_TIME_BASED = { data: { ordered: { date: true } } };
var EVENT_NON_TIME_BASED = { data: { ordered: { date: false } } };

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (markerSync) {
sync = markerSync;
}));

describe('basic functionality', function () {
it('is a simple emitter', function () {
expect(sync).to.be.a(SimpleEmitter);
});

it('emits a "sync" event when the hover handler is called', function (done) {
sync.on('sync', function () {
done();
});

sync.hoverHandler()(EVENT_TIME_BASED);
});

it('ignores a call of the hover handler from a non time-based chart', function () {
sync.on('sync', function () {
expect().fail('"sync" event should not be emitted');
});

sync.hoverHandler()(EVENT_NON_TIME_BASED);
});
});
});
8 changes: 5 additions & 3 deletions src/ui/public/vislib/__tests__/visualizations/time_marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('Vislib Time Marker Test Suite', function () {
var opacity = 0.5;
var width = 3;
var customClass = 'custom-time-marker';
var customLayer = 'custom-time-marker-layer';
var dateMathTimes = ['now-1m', 'now-5m', 'now-15m'];
var myTimes = dateMathTimes.map(function (dateMathString) {
return {
Expand Down Expand Up @@ -49,7 +50,7 @@ describe('Vislib Time Marker Test Suite', function () {
domain = [minDomain, maxDomain];
xScale = d3.time.scale().domain(domain).range([0, 500]);
defaultMarker = new TimeMarker(times, xScale, height);
customMarker = new TimeMarker(myTimes, xScale, height);
customMarker = new TimeMarker(myTimes, xScale, height, customLayer);

selection = d3.select('body').append('div').attr('class', 'marker');
selection.datum(series);
Expand All @@ -59,6 +60,7 @@ describe('Vislib Time Marker Test Suite', function () {
selection.remove('*');
selection = null;
defaultMarker = null;
customMarker = null;
});

describe('_isTimeBaseChart method', function () {
Expand Down Expand Up @@ -87,11 +89,11 @@ describe('Vislib Time Marker Test Suite', function () {
});

it('should render the default line', function () {
expect(!!$('line.time-marker').length).to.be(true);
expect(!!$('.time-marker-layer line.time-marker').length).to.be(true);
});

it('should render the custom (user defined) lines', function () {
expect($('line.custom-time-marker').length).to.be(myTimes.length);
expect($('.custom-time-marker-layer line.custom-time-marker').length).to.be(myTimes.length);
});

it('should set the class', function () {
Expand Down
Loading