diff --git a/src/fixtures/mock_ui_state.js b/src/fixtures/mock_ui_state.js new file mode 100644 index 0000000000000..b0e794722f794 --- /dev/null +++ b/src/fixtures/mock_ui_state.js @@ -0,0 +1,15 @@ +define(function (require) { + var _ = require('lodash'); + var keys = {}; + return { + get: function (path, def) { + return keys[path] == null ? def : keys[path]; + }, + set: function (path, val) { + keys[path] = val; + return val; + }, + on: _.noop, + off: _.noop + } +}) \ No newline at end of file diff --git a/src/plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html b/src/plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html index 5a694765f77e5..cfc12aae2631f 100644 --- a/src/plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html +++ b/src/plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html @@ -5,10 +5,4 @@ Show Tooltip -
- -
diff --git a/src/plugins/kibana/public/discover/controllers/discover.js b/src/plugins/kibana/public/discover/controllers/discover.js index 88b40cd678f53..db10c46952754 100644 --- a/src/plugins/kibana/public/discover/controllers/discover.js +++ b/src/plugins/kibana/public/discover/controllers/discover.js @@ -110,6 +110,8 @@ define(function (require) { } var $state = $scope.state = new AppState(getStateDefaults()); + $scope.uiState = $state.makeStateful('uiState'); + function getStateDefaults() { return { query: $scope.searchSource.get('query') || '', diff --git a/src/plugins/kibana/public/discover/index.html b/src/plugins/kibana/public/discover/index.html index ac17f7bf80d43..47156a890eb1d 100644 --- a/src/plugins/kibana/public/discover/index.html +++ b/src/plugins/kibana/public/discover/index.html @@ -176,7 +176,7 @@

Searching

- +
diff --git a/src/plugins/table_vis/public/__tests__/_table_vis.js b/src/plugins/table_vis/public/__tests__/_table_vis.js index e2068932f61cc..14790fbe83fd1 100644 --- a/src/plugins/table_vis/public/__tests__/_table_vis.js +++ b/src/plugins/table_vis/public/__tests__/_table_vis.js @@ -30,7 +30,8 @@ describe('Integration', function () { $rootScope.vis = vis; $rootScope.esResponse = esResponse; - $el = $(''); + $rootScope.uiState = require('fixtures/mock_ui_state'); + $el = $(''); $compile($el)($rootScope); $rootScope.$apply(); diff --git a/src/ui/public/Vis/Renderbot.js b/src/ui/public/Vis/Renderbot.js index 27ba9d2bd2c53..a491065889fb2 100644 --- a/src/ui/public/Vis/Renderbot.js +++ b/src/ui/public/Vis/Renderbot.js @@ -9,9 +9,10 @@ define(function (require) { * @param {Vis} vis - the vis object that contains all configuration data required to render the vis * @param {jQuery} $el - a jQuery wrapped element to render into */ - function Renderbot(vis, $el) { + function Renderbot(vis, $el, uiState) { this.vis = vis; this.$el = $el; + this.uiState = uiState; } /** diff --git a/src/ui/public/Vis/__tests__/_Renderbot.js b/src/ui/public/Vis/__tests__/_Renderbot.js index a1bfd462fa0a1..2ef792fcf0817 100644 --- a/src/ui/public/Vis/__tests__/_Renderbot.js +++ b/src/ui/public/Vis/__tests__/_Renderbot.js @@ -15,12 +15,14 @@ describe('renderbot', function () { var vis; var $el; var renderbot; + var uiState; beforeEach(init); beforeEach(function () { vis = { hello: 'world' }; $el = 'element'; - renderbot = new Renderbot(vis, $el); + uiState = {}; + renderbot = new Renderbot(vis, $el, uiState); }); it('should have expected methods', function () { diff --git a/src/ui/public/filter_bar/filter_bar_click_handler.js b/src/ui/public/filter_bar/filter_bar_click_handler.js index 6a5c7bbf18854..59844f9207bf7 100644 --- a/src/ui/public/filter_bar/filter_bar_click_handler.js +++ b/src/ui/public/filter_bar/filter_bar_click_handler.js @@ -2,21 +2,11 @@ define(function (require) { var _ = require('lodash'); var dedupFilters = require('./lib/dedupFilters'); var uniqFilters = require('./lib/uniqFilters'); - - // given an object or array of objects, return the value of the passed param - // if the param is missing, return undefined - function findByParam(values, param) { - if (_.isArray(values)) { // point series chart - var index = _.findIndex(values, param); - if (index === -1) return; - return values[index][param]; - } - return values[param]; // pie chart - } + var findByParam = require('ui/utils/find_by_param'); return function (Notifier) { return function ($state) { - return function (event) { + return function (event, simulate) { var notify = new Notifier({ location: 'Filter bar' }); @@ -58,9 +48,20 @@ define(function (require) { if (!filters.length) return; + if (event.negate) { + _.each(filters, function (filter) { + filter.meta = filter.meta || {}; + filter.meta.negate = true; + }); + } + filters = dedupFilters($state.filters, uniqFilters(filters)); // We need to add a bunch of filter deduping here. - $state.$newFilters = filters; + if (!simulate) { + $state.$newFilters = filters; + } + + return filters; } }; }; diff --git a/src/ui/public/template_vis_type/TemplateRenderbot.js b/src/ui/public/template_vis_type/TemplateRenderbot.js index 104036a37d696..18def2803ad45 100644 --- a/src/ui/public/template_vis_type/TemplateRenderbot.js +++ b/src/ui/public/template_vis_type/TemplateRenderbot.js @@ -4,8 +4,8 @@ define(function (require) { var Renderbot = Private(require('ui/Vis/Renderbot')); _.class(TemplateRenderbot).inherits(Renderbot); - function TemplateRenderbot(vis, $el) { - TemplateRenderbot.Super.call(this, vis, $el); + function TemplateRenderbot(vis, $el, uiState) { + TemplateRenderbot.Super.call(this, vis, $el, uiState); this.$scope = $rootScope.$new(); this.$scope.vis = vis; diff --git a/src/ui/public/template_vis_type/TemplateVisType.js b/src/ui/public/template_vis_type/TemplateVisType.js index d4419d2eba969..a025fc3f8814c 100644 --- a/src/ui/public/template_vis_type/TemplateVisType.js +++ b/src/ui/public/template_vis_type/TemplateVisType.js @@ -14,8 +14,8 @@ define(function (require) { } } - TemplateVisType.prototype.createRenderbot = function (vis, $el) { - return new TemplateRenderbot(vis, $el); + TemplateVisType.prototype.createRenderbot = function (vis, $el, uiState) { + return new TemplateRenderbot(vis, $el, uiState); }; return TemplateVisType; diff --git a/src/ui/public/utils/find_by_param.js b/src/ui/public/utils/find_by_param.js new file mode 100644 index 0000000000000..c22a5c6ed00f7 --- /dev/null +++ b/src/ui/public/utils/find_by_param.js @@ -0,0 +1,13 @@ +define(function (require) { + var _ = require('lodash'); + // given an object or array of objects, return the value of the passed param + // if the param is missing, return undefined + return function findByParam(values, param) { + if (_.isArray(values)) { // point series chart + var index = _.findIndex(values, param); + if (index === -1) return; + return values[index][param]; + } + return values[param]; // pie chart + }; +}); \ No newline at end of file diff --git a/src/ui/public/vislib/__tests__/components/color.js b/src/ui/public/vislib/__tests__/components/color.js index 47bec5d4ede6b..5ec45e3e6a1e0 100644 --- a/src/ui/public/vislib/__tests__/components/color.js +++ b/src/ui/public/vislib/__tests__/components/color.js @@ -30,7 +30,7 @@ describe('Vislib Color Module Test Suite', function () { seedColors = Private(require('ui/vislib/components/color/seed_colors')); getColors = Private(require('ui/vislib/components/color/color')); mappedColors = Private(require('ui/vislib/components/color/mapped_colors')); - color = getColors(arr); + color = getColors(arr, {}); })); afterEach(ngMock.inject((config) => { @@ -110,6 +110,11 @@ describe('Vislib Color Module Test Suite', function () { it('should return the value from the mapped colors', function () { expect(color(arr[1])).to.be(mappedColors.get(arr[1])); }); + + it('should return the value from the specified color mapping overrides', function () { + const colorFn = getColors(arr, {good: 'red'}); + expect(colorFn('good')).to.be('red'); + }); }); describe('Seed Colors', function () { diff --git a/src/ui/public/vislib/__tests__/lib/axis_title.js b/src/ui/public/vislib/__tests__/lib/axis_title.js index 19c58829f3cc9..b5e45e9f599ee 100644 --- a/src/ui/public/vislib/__tests__/lib/axis_title.js +++ b/src/ui/public/vislib/__tests__/lib/axis_title.js @@ -8,6 +8,7 @@ var expect = require('expect.js'); describe('Vislib AxisTitle Class Test Suite', function () { var AxisTitle; var Data; + var PersistedState; var axisTitle; var el; var dataObj; @@ -76,6 +77,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { beforeEach(ngMock.inject(function (Private) { AxisTitle = Private(require('ui/vislib/lib/axis_title')); Data = Private(require('ui/vislib/lib/data')); + PersistedState = Private(require('ui/persisted_state/persisted_state')); el = d3.select('body').append('div') .attr('class', 'vis-wrapper'); @@ -91,7 +93,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { .style('width', '20px'); - dataObj = new Data(data, {}); + dataObj = new Data(data, {}, new PersistedState()); xTitle = dataObj.get('xAxisLabel'); yTitle = dataObj.get('yAxisLabel'); axisTitle = new AxisTitle($('.vis-wrapper')[0], xTitle, yTitle); diff --git a/src/ui/public/vislib/__tests__/lib/chart_title.js b/src/ui/public/vislib/__tests__/lib/chart_title.js index cb5a347829b13..8a79ad901585d 100644 --- a/src/ui/public/vislib/__tests__/lib/chart_title.js +++ b/src/ui/public/vislib/__tests__/lib/chart_title.js @@ -8,6 +8,7 @@ var expect = require('expect.js'); describe('Vislib ChartTitle Class Test Suite', function () { var ChartTitle; var Data; + var persistedState; var chartTitle; var el; var dataObj; @@ -74,6 +75,7 @@ describe('Vislib ChartTitle Class Test Suite', function () { beforeEach(ngMock.inject(function (Private) { ChartTitle = Private(require('ui/vislib/lib/chart_title')); Data = Private(require('ui/vislib/lib/data')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); el = d3.select('body').append('div') .attr('class', 'vis-wrapper') @@ -83,7 +85,7 @@ describe('Vislib ChartTitle Class Test Suite', function () { .attr('class', 'chart-title') .style('height', '20px'); - dataObj = new Data(data, {}); + dataObj = new Data(data, {}, persistedState); chartTitle = new ChartTitle($('.vis-wrapper')[0], 'rows'); })); diff --git a/src/ui/public/vislib/__tests__/lib/data.js b/src/ui/public/vislib/__tests__/lib/data.js index 035cb36634fd7..83ee9f3ba4d55 100644 --- a/src/ui/public/vislib/__tests__/lib/data.js +++ b/src/ui/public/vislib/__tests__/lib/data.js @@ -101,10 +101,12 @@ var colsData = { describe('Vislib Data Class Test Suite', function () { var Data; + var persistedState; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { Data = Private(require('ui/vislib/lib/data')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); })); describe('Data Class (main)', function () { @@ -113,7 +115,7 @@ describe('Vislib Data Class Test Suite', function () { }); it('should return an object', function () { - var rowIn = new Data(rowsData, {}); + var rowIn = new Data(rowsData, {}, persistedState); expect(_.isObject(rowIn)).to.be(true); }); @@ -128,7 +130,7 @@ describe('Vislib Data Class Test Suite', function () { ], 'yAxisLabel': 'customLabel' }; - var modifiedData = new Data(seriesDataWithoutLabelInSeries, {}); + var modifiedData = new Data(seriesDataWithoutLabelInSeries, {}, persistedState); expect(modifiedData.data.series[0].label).to.be('customLabel'); }); @@ -158,7 +160,7 @@ describe('Vislib Data Class Test Suite', function () { ], }; - var modifiedData = new Data(seriesDataWithoutLabelInRow, {}); + var modifiedData = new Data(seriesDataWithoutLabelInRow, {}, persistedState); expect(modifiedData.data.rows[0].series[0].label).to.be('customLabel'); expect(modifiedData.data.rows[1].series[0].label).to.be('customLabel'); }); @@ -190,7 +192,7 @@ describe('Vislib Data Class Test Suite', function () { 'yAxisLabel': 'customLabel' }; - var modifiedData = new Data(seriesDataWithoutLabelInRow, {}); + var modifiedData = new Data(seriesDataWithoutLabelInRow, {}, persistedState); expect(modifiedData.data.columns[0].series[0].label).to.be('customLabel'); expect(modifiedData.data.columns[1].series[0].label).to.be('customLabel'); }); @@ -210,7 +212,7 @@ describe('Vislib Data Class Test Suite', function () { }; beforeEach(function () { - data = new Data(pieData, {}); + data = new Data(pieData, {}, persistedState); }); it('should remove zero values', function () { @@ -228,9 +230,9 @@ describe('Vislib Data Class Test Suite', function () { var colOut; beforeEach(function () { - serIn = new Data(seriesData, {}); - rowIn = new Data(rowsData, {}); - colIn = new Data(colsData, {}); + serIn = new Data(seriesData, {}, persistedState); + rowIn = new Data(rowsData, {}, persistedState); + colIn = new Data(colsData, {}, persistedState); serOut = serIn.flatten(); rowOut = rowIn.flatten(); colOut = colIn.flatten(); @@ -246,7 +248,7 @@ describe('Vislib Data Class Test Suite', function () { function testLength(inputData) { return function () { - var data = new Data(inputData, {}); + var data = new Data(inputData, {}, persistedState); var len = _.reduce(data.chartData(), function (sum, chart) { return sum + chart.series.reduce(function (sum, series) { return sum + series.values.length; @@ -267,9 +269,9 @@ describe('Vislib Data Class Test Suite', function () { var minValueStacked = 15; beforeEach(function () { - visData = new Data(dataSeries, {}); - visDataNeg = new Data(dataSeriesNeg, {}); - visDataStacked = new Data(dataStacked, { type: 'histogram' }); + visData = new Data(dataSeries, {}, persistedState); + visDataNeg = new Data(dataSeriesNeg, {}, persistedState); + visDataStacked = new Data(dataStacked, { type: 'histogram' }, persistedState); }); // The first value in the time series is less than the min date in the @@ -304,9 +306,9 @@ describe('Vislib Data Class Test Suite', function () { var maxValueStacked = 115; beforeEach(function () { - visData = new Data(dataSeries, {}); - visDataNeg = new Data(dataSeriesNeg, {}); - visDataStacked = new Data(dataStacked, { type: 'histogram' }); + visData = new Data(dataSeries, {}, persistedState); + visDataNeg = new Data(dataSeriesNeg, {}, persistedState); + visDataStacked = new Data(dataStacked, { type: 'histogram' }, persistedState); }); // The first value in the time series is less than the min date in the @@ -372,7 +374,7 @@ describe('Vislib Data Class Test Suite', function () { }; beforeEach(function () { - data = new Data(geohashGridData, {}); + data = new Data(geohashGridData, {}, persistedState); }); describe('getVisData', function () { @@ -393,7 +395,7 @@ describe('Vislib Data Class Test Suite', function () { describe('null value check', function () { it('should return false', function () { - var data = new Data(rowsData, {}); + var data = new Data(rowsData, {}, persistedState); expect(data.hasNullValues()).to.be(false); }); @@ -409,7 +411,7 @@ describe('Vislib Data Class Test Suite', function () { ] }); - var data = new Data(nullRowData, {}); + var data = new Data(nullRowData, {}, persistedState); expect(data.hasNullValues()).to.be(true); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/dispatch.js b/src/ui/public/vislib/__tests__/lib/dispatch.js index 7025f7dd9a874..668afd5b7280d 100644 --- a/src/ui/public/vislib/__tests__/lib/dispatch.js +++ b/src/ui/public/vislib/__tests__/lib/dispatch.js @@ -21,12 +21,14 @@ describe('Vislib Dispatch Class Test Suite', function () { describe('', function () { var vis; + var persistedState; var SimpleEmitter; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(); - vis.render(data); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); + vis.render(data, persistedState); SimpleEmitter = require('ui/utils/SimpleEmitter'); })); @@ -45,12 +47,14 @@ describe('Vislib Dispatch Class Test Suite', function () { describe('Stock event handlers', function () { var vis; + var persistedState; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); vis.on('brush', _.noop); - vis.render(data); + vis.render(data, persistedState); })); afterEach(function () { @@ -114,12 +118,14 @@ describe('Vislib Dispatch Class Test Suite', function () { describe('Custom event handlers', function () { it('should attach whatever gets passed on vis.on() to chart.events', function (done) { var vis; + var persistedState; var chart; ngMock.module('kibana'); ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); vis.on('someEvent', _.noop); - vis.render(data); + vis.render(data, persistedState); vis.handler.charts.forEach(function (chart) { expect(chart.events.listenerCount('someEvent')).to.be(1); @@ -132,11 +138,13 @@ describe('Vislib Dispatch Class Test Suite', function () { it('can be added after rendering', function () { var vis; + var persistedState; var chart; ngMock.module('kibana'); ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(); - vis.render(data); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); + vis.render(data, persistedState); vis.on('someEvent', _.noop); vis.handler.charts.forEach(function (chart) { diff --git a/src/ui/public/vislib/__tests__/lib/handler/handler.js b/src/ui/public/vislib/__tests__/lib/handler/handler.js index 78bdd8462ddb9..ba09f34823e04 100644 --- a/src/ui/public/vislib/__tests__/lib/handler/handler.js +++ b/src/ui/public/vislib/__tests__/lib/handler/handler.js @@ -26,6 +26,7 @@ dateHistogramArray.forEach(function (data, i) { describe('Vislib Handler Test Suite for ' + names[i] + ' Data', function () { var Handler; var vis; + var persistedState; var events = [ 'click', 'brush' @@ -35,7 +36,8 @@ dateHistogramArray.forEach(function (data, i) { beforeEach(ngMock.inject(function (Private) { Handler = Private(require('ui/vislib/lib/handler/handler')); vis = Private(require('fixtures/vislib/_vis_fixture'))(); - vis.render(data); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); + vis.render(data, persistedState); })); afterEach(function () { diff --git a/src/ui/public/vislib/__tests__/lib/layout/layout.js b/src/ui/public/vislib/__tests__/lib/layout/layout.js index 61b19bf2ce5d0..f10949009c951 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/layout.js +++ b/src/ui/public/vislib/__tests__/lib/layout/layout.js @@ -26,6 +26,7 @@ dateHistogramArray.forEach(function (data, i) { describe('Vislib Layout Class Test Suite for ' + names[i] + ' Data', function () { var Layout; var vis; + var persistedState; var numberOfCharts; var testLayout; @@ -35,7 +36,8 @@ dateHistogramArray.forEach(function (data, i) { ngMock.inject(function (Private) { Layout = Private(require('ui/vislib/lib/layout/layout')); vis = Private(require('fixtures/vislib/_vis_fixture'))(); - vis.render(data); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); + vis.render(data, persistedState); numberOfCharts = vis.handler.charts.length; }); }); @@ -50,7 +52,6 @@ dateHistogramArray.forEach(function (data, i) { expect($(vis.el).find('.vis-wrapper').length).to.be(1); expect($(vis.el).find('.y-axis-col-wrapper').length).to.be(1); expect($(vis.el).find('.vis-col-wrapper').length).to.be(1); - expect($(vis.el).find('.legend-col-wrapper').length).to.be(1); expect($(vis.el).find('.y-axis-col').length).to.be(1); expect($(vis.el).find('.y-axis-title').length).to.be(1); expect($(vis.el).find('.y-axis-div-wrapper').length).to.be(1); diff --git a/src/ui/public/vislib/__tests__/lib/legend.js b/src/ui/public/vislib/__tests__/lib/legend.js deleted file mode 100644 index a0b8140de1e44..0000000000000 --- a/src/ui/public/vislib/__tests__/lib/legend.js +++ /dev/null @@ -1,127 +0,0 @@ -var d3 = require('d3'); -var angular = require('angular'); -var _ = require('lodash'); -var $ = require('jquery'); -var ngMock = require('ngMock'); -var expect = require('expect.js'); - -var slices = require('fixtures/vislib/mock_data/histogram/_slices'); -var stackedSeries = require('fixtures/vislib/mock_data/date_histogram/_stacked_series'); -var histogramSlices = require('fixtures/vislib/mock_data/histogram/_slices'); - -var dataArray = [ - stackedSeries, - slices, - histogramSlices, - stackedSeries, - stackedSeries -]; - -var chartTypes = [ - 'histogram', - 'pie', - 'pie', - 'area', - 'line' -]; - -var chartSelectors = { - histogram: '.chart rect', - pie: '.chart path', - area: '.chart path', - line: '.chart circle' -}; - -describe('Vislib Legend Class', function () { - dataArray.forEach(function (data, i) { - describe(chartTypes[i] + ' data', function () { - var visLibParams = { - type: chartTypes[i], - addLegend: true - }; - var Legend; - var vis; - var $el; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - vis = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams); - Legend = Private(require('ui/vislib/lib/legend')); - $el = d3.select('body').append('div').attr('class', 'fake-legend'); - vis.render(data); - })); - - afterEach(function () { - $(vis.el).remove(); - $('.fake-legend').remove(); - vis = null; - }); - - describe('legend item label matches vis item label', function () { - it('should match the slice label', function () { - var chartType = chartTypes[i]; - var paths = $(vis.el).find(chartSelectors[chartType]).toArray(); - var items = vis.handler.legend.labels; - - items.forEach(function (d) { - var path = _.find(paths, function (path) { - return path.getAttribute('data-label') === String(d.label); - }); - - expect(path).to.be.ok(); - }); - }); - }); - - describe('header method', function () { - it('should append the legend header', function () { - expect($(vis.el).find('.header').length).to.be(1); - expect($(vis.el).find('.column-labels').length).to.be(1); - }); - }); - - describe('list method', function () { - it('should append the legend list', function () { - expect($(vis.el).find('.legend-ul').length).to.be(1); - }); - - it('should contain a list of items', function () { - expect($(vis.el).find('li').length).to.be.greaterThan(1); - }); - - it('should not return an undefined value', function () { - var emptyObject = { - label: '' - }; - var labels = [emptyObject, emptyObject, emptyObject]; - var args = { - _attr: {isOpen: true}, - color: function () { return 'blue'; } - }; - - Legend.prototype._list($el, labels, args); - - $el.selectAll('li').each(function (d) { - expect(d.label).not.to.be(undefined); - }); - }); - }); - - describe('render method', function () { - it('should create a legend toggle', function () { - expect($('.legend-toggle').length).to.be(1); - }); - - it('should have an onclick listener', function () { - expect(!!$('.legend-toggle')[0].__onclick).to.be(true); - expect(!!$('li.color')[0].__onclick).to.be(true); - }); - - it('should attach onmouseover listener', function () { - expect(!!$('li.color')[0].__onmouseover).to.be(true); - }); - }); - }); - }); -}); - diff --git a/src/ui/public/vislib/__tests__/lib/x_axis.js b/src/ui/public/vislib/__tests__/lib/x_axis.js index 36d2620fa0a6b..a338f0ac34665 100644 --- a/src/ui/public/vislib/__tests__/lib/x_axis.js +++ b/src/ui/public/vislib/__tests__/lib/x_axis.js @@ -8,6 +8,7 @@ var expect = require('expect.js'); describe('Vislib xAxis Class Test Suite', function () { var XAxis; var Data; + var persistedState; var xAxis; var el; var fixture; @@ -77,6 +78,7 @@ describe('Vislib xAxis Class Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { Data = Private(require('ui/vislib/lib/data')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); XAxis = Private(require('ui/vislib/lib/x_axis')); el = d3.select('body').append('div') @@ -86,7 +88,7 @@ describe('Vislib xAxis Class Test Suite', function () { fixture = el.append('div') .attr('class', 'x-axis-div'); - dataObj = new Data(data, {}); + dataObj = new Data(data, {}, persistedState); xAxis = new XAxis({ el: $('.x-axis-div')[0], xValues: dataObj.xValues(), diff --git a/src/ui/public/vislib/__tests__/lib/y_axis.js b/src/ui/public/vislib/__tests__/lib/y_axis.js index 3bb97e55e0fab..e32d0da0ea72d 100644 --- a/src/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/ui/public/vislib/__tests__/lib/y_axis.js @@ -6,6 +6,7 @@ var expect = require('expect.js'); var YAxis; var Data; +var persistedState; var el; var buildYAxis; var yAxis; @@ -70,7 +71,7 @@ function createData(seriesData) { var dataObj = new Data(data, { defaultYMin: true - }); + }, persistedState); buildYAxis = function (params) { return new YAxis(_.merge({}, params, { @@ -94,6 +95,7 @@ describe('Vislib yAxis Class Test Suite', function () { beforeEach(ngMock.inject(function (Private) { Data = Private(require('ui/vislib/lib/data')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); YAxis = Private(require('ui/vislib/lib/y_axis')); expect($('.y-axis-wrapper')).to.have.length(0); diff --git a/src/ui/public/vislib/__tests__/vis.js b/src/ui/public/vislib/__tests__/vis.js index f5d14fcae5b04..38a7732d90d38 100644 --- a/src/ui/public/vislib/__tests__/vis.js +++ b/src/ui/public/vislib/__tests__/vis.js @@ -29,12 +29,14 @@ dataArray.forEach(function (data, i) { var beforeEvent = 'click'; var afterEvent = 'brush'; var vis; + var persistedState; var secondVis; var numberOfCharts; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); secondVis = Private(require('fixtures/vislib/_vis_fixture'))(); })); @@ -46,7 +48,7 @@ dataArray.forEach(function (data, i) { describe('render Method', function () { beforeEach(function () { - vis.render(data); + vis.render(data, persistedState); numberOfCharts = vis.handler.charts.length; }); @@ -66,7 +68,7 @@ dataArray.forEach(function (data, i) { describe('resize Method', function () { beforeEach(function () { - vis.render(data); + vis.render(data, persistedState); vis.resize(); numberOfCharts = vis.handler.charts.length; }); @@ -85,8 +87,8 @@ dataArray.forEach(function (data, i) { describe('destroy Method', function () { beforeEach(function () { - vis.render(data); - secondVis.render(data); + vis.render(data, persistedState); + secondVis.render(data, persistedState); secondVis.destroy(); }); @@ -101,7 +103,7 @@ dataArray.forEach(function (data, i) { describe('set Method', function () { beforeEach(function () { - vis.render(data); + vis.render(data, persistedState); vis.set('addLegend', false); vis.set('offset', 'wiggle'); }); @@ -114,7 +116,7 @@ dataArray.forEach(function (data, i) { describe('get Method', function () { beforeEach(function () { - vis.render(data); + vis.render(data, persistedState); }); it('should get attribue values', function () { @@ -145,7 +147,7 @@ dataArray.forEach(function (data, i) { }); // Render chart - vis.render(data); + vis.render(data, persistedState); // Add event after charts have rendered listeners.forEach(function (listener) { @@ -199,7 +201,7 @@ dataArray.forEach(function (data, i) { vis.off(beforeEvent, listener1); // Render chart - vis.render(data); + vis.render(data, persistedState); // Add event after charts have rendered listeners.forEach(function (listener) { diff --git a/src/ui/public/vislib/__tests__/visualizations/area_chart.js b/src/ui/public/vislib/__tests__/visualizations/area_chart.js index 66e6e292ba59e..07245e525007d 100644 --- a/src/ui/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/area_chart.js @@ -26,12 +26,14 @@ var visLibParams = { _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { describe('Vislib Area Chart Test Suite for ' + imaVariable + ' Data', function () { var vis; + var persistedState; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); vis.on('brush', _.noop); - vis.render(variablesAreCool); + vis.render(variablesAreCool, persistedState); })); afterEach(function () { @@ -42,7 +44,7 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { describe('checkIfEnoughData method throws an error when not enough data', function () { beforeEach(function () { ngMock.inject(function () { - vis.render(notQuiteEnoughVariables); + vis.render(notQuiteEnoughVariables, persistedState); }); }); @@ -58,7 +60,7 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { describe('checkIfEnoughData method should not throw an error when enough data', function () { beforeEach(function () { ngMock.inject(function () { - vis.render(woahLotsOfVariables); + vis.render(woahLotsOfVariables, persistedState); }); }); @@ -215,7 +217,7 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { describe('defaultYExtents is true', function () { beforeEach(function () { vis._attr.defaultYExtents = true; - vis.render(variablesAreCool); + vis.render(variablesAreCool, persistedState); }); it('should return yAxis extents equal to data extents', function () { diff --git a/src/ui/public/vislib/__tests__/visualizations/chart.js b/src/ui/public/vislib/__tests__/visualizations/chart.js index 8fb2e758d45bb..b4c5b88b620ed 100644 --- a/src/ui/public/vislib/__tests__/visualizations/chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/chart.js @@ -7,6 +7,7 @@ describe('Vislib _chart Test Suite', function () { var ColumnChart; var Chart; var Data; + var persistedState; var Vis; var chartData = {}; var vis; @@ -82,6 +83,7 @@ describe('Vislib _chart Test Suite', function () { beforeEach(ngMock.inject(function (Private) { Vis = Private(require('ui/vislib/vis')); Data = Private(require('ui/vislib/lib/data')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); ColumnChart = Private(require('ui/vislib/visualizations/column_chart')); Chart = Private(require('ui/vislib/visualizations/_chart')); @@ -96,7 +98,7 @@ describe('Vislib _chart Test Suite', function () { }; vis = new Vis(el[0][0], config); - vis.data = new Data(data, config); + vis.data = new Data(data, config, persistedState); myChart = new ColumnChart(vis, el, chartData); })); diff --git a/src/ui/public/vislib/__tests__/visualizations/column_chart.js b/src/ui/public/vislib/__tests__/visualizations/column_chart.js index 66ff249092ddf..78351a44578cb 100644 --- a/src/ui/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/column_chart.js @@ -31,6 +31,7 @@ dataTypesArray.forEach(function (dataType, i) { describe('Vislib Column Chart Test Suite for ' + name + ' Data', function () { var vis; + var persistedState; var visLibParams = { type: 'histogram', hasTimeField: true, @@ -42,8 +43,9 @@ dataTypesArray.forEach(function (dataType, i) { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); vis.on('brush', _.noop); - vis.render(data); + vis.render(data, persistedState); })); afterEach(function () { @@ -90,7 +92,7 @@ dataTypesArray.forEach(function (dataType, i) { describe('updateBars method', function () { beforeEach(function () { vis.handler._attr.mode = 'grouped'; - vis.render(vis.data); + vis.render(vis.data, persistedState); }); it('should returned grouped bars', function () { @@ -184,7 +186,7 @@ dataTypesArray.forEach(function (dataType, i) { describe('defaultYExtents is true', function () { beforeEach(function () { vis._attr.defaultYExtents = true; - vis.render(data); + vis.render(data, persistedState); }); it('should return yAxis extents equal to data extents', function () { diff --git a/src/ui/public/vislib/__tests__/visualizations/line_chart.js b/src/ui/public/vislib/__tests__/visualizations/line_chart.js index 4b169437736e2..46c6529c7fd66 100644 --- a/src/ui/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/line_chart.js @@ -29,6 +29,7 @@ describe('Vislib Line Chart', function () { describe(name + ' Data', function () { var vis; + var persistedState; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { @@ -40,8 +41,9 @@ describe('Vislib Line Chart', function () { }; vis = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); vis.on('brush', _.noop); - vis.render(data); + vis.render(data, persistedState); })); afterEach(function () { @@ -165,7 +167,7 @@ describe('Vislib Line Chart', function () { describe('defaultYExtents is true', function () { beforeEach(function () { vis._attr.defaultYExtents = true; - vis.render(data); + vis.render(data, persistedState); }); it('should return yAxis extents equal to data extents', function () { diff --git a/src/ui/public/vislib/__tests__/visualizations/pie_chart.js b/src/ui/public/vislib/__tests__/visualizations/pie_chart.js index 86353f1de78ad..d7da631189466 100644 --- a/src/ui/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/pie_chart.js @@ -63,6 +63,7 @@ describe('No global chart settings', function () { var chart1; var chart2; var Vis; + var persistedState; var indexPattern; var buildHierarchicalData; var data1; @@ -73,6 +74,7 @@ describe('No global chart settings', function () { chart1 = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams1); chart2 = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams2); Vis = Private(require('ui/Vis')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); buildHierarchicalData = Private(require('ui/agg_response/hierarchical/build_hierarchical_data')); @@ -98,8 +100,8 @@ describe('No global chart settings', function () { data1 = buildHierarchicalData(stubVis1, fixtures.threeTermBuckets); data2 = buildHierarchicalData(stubVis2, fixtures.threeTermBuckets); - chart1.render(data1); - chart2.render(data2); + chart1.render(data1, persistedState); + chart2.render(data2, persistedState); })); afterEach(function () { @@ -157,6 +159,7 @@ aggArray.forEach(function (dataAgg, i) { }; var vis; var Vis; + var persistedState; var indexPattern; var buildHierarchicalData; var data; @@ -165,6 +168,7 @@ aggArray.forEach(function (dataAgg, i) { beforeEach(ngMock.inject(function (Private) { vis = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams); Vis = Private(require('ui/Vis')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); buildHierarchicalData = Private(require('ui/agg_response/hierarchical/build_hierarchical_data')); @@ -179,7 +183,7 @@ aggArray.forEach(function (dataAgg, i) { data = buildHierarchicalData(stubVis, fixtures.threeTermBuckets); - vis.render(data); + vis.render(data, persistedState); })); afterEach(function () { diff --git a/src/ui/public/vislib/components/color/color.js b/src/ui/public/vislib/components/color/color.js index 29fe7fac327a6..b01683b4ce56c 100644 --- a/src/ui/public/vislib/components/color/color.js +++ b/src/ui/public/vislib/components/color/color.js @@ -10,7 +10,8 @@ define(function (require) { * and returns a hex color associated with that value. */ - return function (arrayOfStringsOrNumbers) { + return function (arrayOfStringsOrNumbers, colorMapping) { + colorMapping = colorMapping || {}; if (!_.isArray(arrayOfStringsOrNumbers)) { throw new Error('ColorUtil expects an array'); } @@ -24,7 +25,7 @@ define(function (require) { mappedColors.mapKeys(arrayOfStringsOrNumbers); return function (value) { - return mappedColors.get(value); + return colorMapping[value] || mappedColors.get(value); }; }; }; diff --git a/src/ui/public/vislib/lib/data.js b/src/ui/public/vislib/lib/data.js index 743b8eeeec747..756540b50b470 100644 --- a/src/ui/public/vislib/lib/data.js +++ b/src/ui/public/vislib/lib/data.js @@ -18,11 +18,13 @@ define(function (require) { * @param data {Object} Elasticsearch query results * @param attr {Object|*} Visualization options */ - function Data(data, attr) { + function Data(data, attr, uiState) { if (!(this instanceof Data)) { - return new Data(data, attr); + return new Data(data, attr, uiState); } + this.uiState = uiState; + var self = this; var offset; @@ -40,7 +42,7 @@ define(function (require) { this.type = this.getDataType(); this.labels = this._getLabels(this.data); - this.color = this.labels ? color(this.labels) : undefined; + this.color = this.labels ? color(this.labels, uiState.get('vis.colors')) : undefined; this._normalizeOrdered(); this._attr = _.defaults(attr || {}, { @@ -646,7 +648,7 @@ define(function (require) { * @returns {Function} Performs lookup on string and returns hex color */ Data.prototype.getColorFunc = function () { - return color(this.getLabels()); + return color(this.getLabels(), this.uiState.get('vis.colors')); }; /** @@ -658,7 +660,7 @@ define(function (require) { Data.prototype.getPieColorFunc = function () { return color(this.pieNames(this.getVisData()).map(function (d) { return d.label; - })); + }), this.uiState.get('vis.colors')); }; /** diff --git a/src/ui/public/vislib/lib/dispatch.js b/src/ui/public/vislib/lib/dispatch.js index d50cb3dc34775..489d3af07f0de 100644 --- a/src/ui/public/vislib/lib/dispatch.js +++ b/src/ui/public/vislib/lib/dispatch.js @@ -229,16 +229,8 @@ define(function (require) { */ Dispatch.prototype.highlightLegend = function (element) { var label = this.getAttribute('data-label'); - if (!label) return; - - d3.select(element) - .select('.legend-ul') - .selectAll('li.color') - .filter(function (d, i) { - return String(d.label) !== label; - }) - .classed('blur_shape', true); + $('[data-label]', element.parentNode).not('[data-label="' + label + '"]').css('opacity', 0.5); }; /** @@ -248,10 +240,7 @@ define(function (require) { * @method unHighlightLegend */ Dispatch.prototype.unHighlightLegend = function (element) { - d3.select(element) - .select('.legend-ul') - .selectAll('li.color') - .classed('blur_shape', false); + $('[data-label]', element.parentNode).css('opacity', 1); }; /** diff --git a/src/ui/public/vislib/lib/handler/handler.js b/src/ui/public/vislib/lib/handler/handler.js index c711e9cf49b94..8b7ba300bdbcd 100644 --- a/src/ui/public/vislib/lib/handler/handler.js +++ b/src/ui/public/vislib/lib/handler/handler.js @@ -7,7 +7,6 @@ define(function (require) { var Data = Private(require('ui/vislib/lib/data')); var Layout = Private(require('ui/vislib/lib/layout/layout')); - var Legend = Private(require('ui/vislib/lib/legend')); /** * Handles building all the components of the visualization @@ -23,7 +22,7 @@ define(function (require) { return new Handler(vis, opts); } - this.data = opts.data || new Data(vis.data, vis._attr); + this.data = opts.data || new Data(vis.data, vis._attr, vis.uiState); this.vis = vis; this.el = vis.el; this.ChartClass = vis.ChartClass; @@ -39,15 +38,10 @@ define(function (require) { this.axisTitle = opts.axisTitle; this.alerts = opts.alerts; - if (this._attr.addLegend) { - this.legend = opts.legend; - } - this.layout = new Layout(vis.el, vis.data, vis._attr.type, opts); this.binder = new Binder(); this.renderArray = _.filter([ this.layout, - this.legend, this.axisTitle, this.chartTitle, this.alerts, @@ -96,12 +90,6 @@ define(function (require) { this._validateData(); this.renderArray.forEach(function (property) { - if (property instanceof Legend) { - self.vis.activeEvents().forEach(function (event) { - self.enable(event, property); - }); - } - if (typeof property.render === 'function') { property.render(); } diff --git a/src/ui/public/vislib/lib/handler/types/pie.js b/src/ui/public/vislib/lib/handler/types/pie.js index 33a29c81257c4..09f8d9cc6c65f 100644 --- a/src/ui/public/vislib/lib/handler/types/pie.js +++ b/src/ui/public/vislib/lib/handler/types/pie.js @@ -2,7 +2,6 @@ define(function (require) { return function PieHandler(Private) { var Handler = Private(require('ui/vislib/lib/handler/handler')); var Data = Private(require('ui/vislib/lib/data')); - var Legend = Private(require('ui/vislib/lib/legend')); var ChartTitle = Private(require('ui/vislib/lib/chart_title')); /* @@ -11,7 +10,6 @@ define(function (require) { return function (vis) { return new Handler(vis, { - legend: new Legend(vis), chartTitle: new ChartTitle(vis.el) }); }; diff --git a/src/ui/public/vislib/lib/handler/types/point_series.js b/src/ui/public/vislib/lib/handler/types/point_series.js index 0762350658bfa..69c345ccaefb3 100644 --- a/src/ui/public/vislib/lib/handler/types/point_series.js +++ b/src/ui/public/vislib/lib/handler/types/point_series.js @@ -3,7 +3,6 @@ define(function (require) { var injectZeros = Private(require('ui/vislib/components/zero_injection/inject_zeros')); var Handler = Private(require('ui/vislib/lib/handler/handler')); var Data = Private(require('ui/vislib/lib/data')); - var Legend = Private(require('ui/vislib/lib/legend')); var XAxis = Private(require('ui/vislib/lib/x_axis')); var YAxis = Private(require('ui/vislib/lib/y_axis')); var AxisTitle = Private(require('ui/vislib/lib/axis_title')); @@ -22,14 +21,13 @@ define(function (require) { var data; if (opts.zeroFill) { - data = new Data(injectZeros(vis.data), vis._attr); + data = new Data(injectZeros(vis.data), vis._attr, vis.uiState); } else { - data = new Data(vis.data, vis._attr); + data = new Data(vis.data, vis._attr, vis.uiState); } return new Handler(vis, { data: data, - legend: new Legend(vis, vis.data), axisTitle: new AxisTitle(vis.el, data.get('xAxisLabel'), data.get('yAxisLabel')), chartTitle: new ChartTitle(vis.el), xAxis: new XAxis({ diff --git a/src/ui/public/vislib/lib/handler/types/tile_map.js b/src/ui/public/vislib/lib/handler/types/tile_map.js index a711886fcd4c0..d7a44874a3160 100644 --- a/src/ui/public/vislib/lib/handler/types/tile_map.js +++ b/src/ui/public/vislib/lib/handler/types/tile_map.js @@ -6,7 +6,7 @@ define(function (require) { var Data = Private(require('ui/vislib/lib/data')); return function (vis) { - var data = new Data(vis.data, vis._attr); + var data = new Data(vis.data, vis._attr, vis.uiState); var MapHandler = new Handler(vis, { data: data diff --git a/src/ui/public/vislib/lib/layout/types/column_layout.js b/src/ui/public/vislib/lib/layout/types/column_layout.js index 0dfea45abfc28..0caa60c5b0299 100644 --- a/src/ui/public/vislib/lib/layout/types/column_layout.js +++ b/src/ui/public/vislib/lib/layout/types/column_layout.js @@ -100,10 +100,6 @@ define(function (require) { ] } ] - }, - { - type: 'div', - class: 'legend-col-wrapper' } ] } diff --git a/src/ui/public/vislib/lib/legend.js b/src/ui/public/vislib/lib/legend.js deleted file mode 100644 index db2ebbe2ab076..0000000000000 --- a/src/ui/public/vislib/lib/legend.js +++ /dev/null @@ -1,210 +0,0 @@ -define(function (require) { - return function LegendFactory(Private) { - var d3 = require('d3'); - var _ = require('lodash'); - var Dispatch = Private(require('ui/vislib/lib/dispatch')); - var Data = Private(require('ui/vislib/lib/data')); - var legendHeaderTemplate = _.template(require('ui/vislib/partials/legend_header.html')); - var dataLabel = require('ui/vislib/lib/_data_label'); - var color = Private(require('ui/vislib/components/color/color')); - - /** - * Appends legend to the visualization - * - * @class Legend - * @constructor - * @param vis {Object} Reference to Vis Constructor - */ - function Legend(vis) { - if (!(this instanceof Legend)) { - return new Legend(vis); - } - - var data = vis.data.columns || vis.data.rows || [vis.data]; - var type = vis._attr.type; - var labels = this.labels = this._getLabels(data, type); - var labelsArray = labels.map(function (obj) { return obj.label; }); - - this.events = new Dispatch(); - this.vis = vis; - this.el = vis.el; - this.color = color(labelsArray); - this._attr = _.defaults({}, vis._attr || {}, { - 'legendClass' : 'legend-col-wrapper', - 'blurredOpacity' : 0.3, - 'focusOpacity' : 1, - 'defaultOpacity' : 1, - 'legendDefaultOpacity': 1 - }); - } - - Legend.prototype._getPieLabels = function (data) { - return Data.prototype.pieNames(data); - }; - - Legend.prototype._getSeriesLabels = function (data) { - var values = data.map(function (chart) { - return chart.series; - }) - .reduce(function (a, b) { - return a.concat(b); - }, []); - - return _.uniq(values, 'label'); - }; - - Legend.prototype._getLabels = function (data, type) { - if (type === 'pie') return this._getPieLabels(data); - return this._getSeriesLabels(data); - }; - - /** - * Adds legend header - * - * @method header - * @param el {HTMLElement} Reference to DOM element - * @param args {Object|*} Legend options - * @returns {*} HTML element - */ - Legend.prototype._header = function (el, args) { - var self = this; - return el.append('div') - .attr('class', 'header') - .append('div') - .attr('class', 'column-labels') - .html(function () { - return legendHeaderTemplate({ isOpen: self.vis.get('legendOpen') }); - }); - }; - - /** - * Adds list to legend - * - * @method list - * @param el {HTMLElement} Reference to DOM element - * @param arrOfLabels {Array} Array of labels - * @param args {Object|*} Legend options - * @returns {D3.Selection} HTML element with list of labels attached - */ - Legend.prototype._list = function (el, data, args) { - var self = this; - - return el.append('ul') - .attr('class', function () { - var className = 'legend-ul'; - if (self.vis && !self.vis.get('legendOpen')) className += ' hidden'; - return className; - }) - .selectAll('li') - .data(data) - .enter() - .append('li') - .attr('class', 'color') - .each(function (d) { - var li = d3.select(this); - self._addIdentifier.call(this, d); - - li.append('i') - .attr('class', 'fa fa-circle dots') - .attr('style', 'color:' + args.color(d.label)); - - li.append('span').text(d.label); - }); - }; - - /** - * Append the data label to the element - * - * @method _addIdentifier - * @param label {string} label to use - */ - Legend.prototype._addIdentifier = function (d) { - dataLabel(this, d.label); - }; - - /** - * Renders legend - * - * @method render - * @return {HTMLElement} Legend - */ - Legend.prototype.render = function () { - var self = this; - var visEl = d3.select(this.el); - var legendDiv = visEl.select('.' + this._attr.legendClass); - var items = this.labels; - this._header(legendDiv, this); - this._list(legendDiv, items, this); - - var headerIcon = visEl.select('.legend-toggle'); - - // toggle legend open and closed - headerIcon - .on('click', function legendClick() { - var legendOpen = !self.vis.get('legendOpen'); - self.vis.set('legendOpen', legendOpen); - - visEl.select('ul.legend-ul').classed('hidden', legendOpen); - self.vis.resize(); - }); - - legendDiv.select('.legend-ul').selectAll('li') - .on('mouseover', function (d) { - var label = d.label; - var charts = visEl.selectAll('.chart'); - - function filterLabel() { - var pointLabel = this.getAttribute('data-label'); - return pointLabel !== label.toString(); - } - - if (label && label !== 'Count') { - d3.select(this).style('cursor', 'pointer'); - } - - // legend - legendDiv.selectAll('li') - .filter(filterLabel) - .classed('blur_shape', true); - - // all data-label attribute - charts.selectAll('[data-label]') - .filter(filterLabel) - .classed('blur_shape', true); - - var eventEl = d3.select(this); - eventEl.style('white-space', 'inherit'); - eventEl.style('word-break', 'break-all'); - }) - .on('mouseout', function () { - /* - * The default opacity of elements in charts may be modified by the - * chart constructor, and so may differ from that of the legend - */ - - var charts = visEl.selectAll('.chart'); - - // legend - legendDiv.selectAll('li') - .classed('blur_shape', false); - - // all data-label attribute - charts.selectAll('[data-label]') - .classed('blur_shape', false); - - var eventEl = d3.select(this); - eventEl.style('white-space', 'nowrap'); - eventEl.style('word-break', 'inherit'); - }); - - legendDiv.selectAll('li.color').each(function (d) { - var label = d.label; - if (label !== undefined && label !== 'Count') { - d3.select(this).call(self.events.addClickEvent()); - } - }); - }; - - return Legend; - }; -}); diff --git a/src/ui/public/vislib/partials/legend_header.html b/src/ui/public/vislib/partials/legend_header.html deleted file mode 100644 index 7c909bcee9920..0000000000000 --- a/src/ui/public/vislib/partials/legend_header.html +++ /dev/null @@ -1,8 +0,0 @@ -
- - <%= (isOpen) ? - 'Legend ' : - '' - %> - -
\ No newline at end of file diff --git a/src/ui/public/vislib/styles/_legend.less b/src/ui/public/vislib/styles/_legend.less index 41f7184db3034..2422aa7cd5db9 100644 --- a/src/ui/public/vislib/styles/_legend.less +++ b/src/ui/public/vislib/styles/_legend.less @@ -1,25 +1,33 @@ @import "~ui/styles/mixins"; @import "~ui/styles/variables"; +visualize-legend { + display: flex; + flex-direction: row; +} + .legend-col-wrapper { .flex-parent(0, 0, auto); + flex-direction: row; z-index: 10; min-height: 0; - .legend-toggle { + .header { cursor: pointer; - opacity: 0.75; - white-space: nowrap; - font-size: 0.9em; + width: 15px; + } - .legend-open { - margin-right: 20px; + .legend-toggle { + &:hover { + color: @sidebar-hover-color; + background-color: @sidebar-hover-bg; } - .legend-closed { - font-size: 1.5em; - padding-left: 5px; - } + background-color: @sidebar-bg; + font-size: 10px; + height: 30px; + padding: 8px 3px; + border-bottom-left-radius: @border-radius-small; } .column-labels { @@ -27,41 +35,85 @@ } .legend-ul { + border-left: 1px solid @sidebar-bg; + width: 150px; flex: 1 1 1px; overflow-x: hidden; - overflow-y: auto; - + overflow-y: scroll; + color: @legend-item-color; list-style-type: none; padding: 0; + margin-bottom: 0; visibility: visible; min-height: 0; - min-width: 60px; - margin-right: 5px; - - li.color { - min-height: 22px; - padding: 3px 0 3px 0; - text-align: left; - font-size: 12px; - line-height: 13px; - color: @legend-item-color; - cursor: default; - text-align: left; - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; - max-width: 150px; - - .dots { - padding-right: 5px; - } - } + font-size: 12px; + line-height: 13px; + text-align: left; + + display: flex; + flex-direction: column; } .legend-ul.hidden { visibility: hidden; } +} - .legend-toggle { +.legend-value { + &-title { + padding: 3px; + + &:hover { + background-color: @sidebar-hover-bg; + } + } + + &-truncate { + overflow-x: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &-full { + white-space: normal; + word-break: break-all; + background-color: @sidebar-hover-bg; + } + + &-details { + border-bottom: 1px solid @sidebar-bg; + + .filter-button { + display: block; + float: left; + width: 50%; + border-radius: 0px; + background-color: @sidebar-bg; + + &:hover { + background-color: @sidebar-hover-bg; + } + } + } + + &-color-picker { + width: 130px; + + .dot { + line-height: 14px; + margin: 2px; + font-size: 14px; + } + + .dot:hover { + margin: 0px; + font-size: 18px + } } } + +.legend-value:hover { + cursor: pointer; +} + + diff --git a/src/ui/public/vislib/vis.js b/src/ui/public/vislib/vis.js index 09819cf8e6f9e..dfdf8c2586451 100644 --- a/src/ui/public/vislib/vis.js +++ b/src/ui/public/vislib/vis.js @@ -45,7 +45,7 @@ define(function (require) { * @method render * @param data {Object} Elasticsearch query results */ - Vis.prototype.render = function (data) { + Vis.prototype.render = function (data, uiState) { var chartType = this._attr.type; if (!data) { @@ -58,6 +58,12 @@ define(function (require) { } this.data = data; + + if (!this.uiState) { + this.uiState = uiState; + uiState.on('change', this._uiStateChangeHandler = () => this.render(this.data, this.uiState)); + } + this.handler = handlerTypes[chartType](this) || handlerTypes.column(this); this._runOnHandler('render'); }; @@ -76,7 +82,7 @@ define(function (require) { if (this.handler && _.isFunction(this.handler.resize)) { this._runOnHandler('resize'); } else { - this.render(this.data); + this.render(this.data, this.uiState); } }; @@ -113,6 +119,7 @@ define(function (require) { this.binder.destroy(); this.resizeChecker.destroy(); + if (this.uiState) this.uiState.off('change', this._uiStateChangeHandler); if (this.handler) this._runOnHandler('destroy'); selection.remove(); @@ -128,7 +135,7 @@ define(function (require) { */ Vis.prototype.set = function (name, val) { this._attr[name] = val; - this.render(this.data); + this.render(this.data, this.uiState); }; /** diff --git a/src/ui/public/vislib_vis_type/VislibRenderbot.js b/src/ui/public/vislib_vis_type/VislibRenderbot.js index 434875ffdba49..18ccadd614515 100644 --- a/src/ui/public/vislib_vis_type/VislibRenderbot.js +++ b/src/ui/public/vislib_vis_type/VislibRenderbot.js @@ -5,8 +5,8 @@ module.exports = function VislibRenderbotFactory(Private) { var buildChartData = Private(require('ui/vislib_vis_type/buildChartData')); _.class(VislibRenderbot).inherits(Renderbot); - function VislibRenderbot(vis, $el) { - VislibRenderbot.Super.call(this, vis, $el); + function VislibRenderbot(vis, $el, uiState) { + VislibRenderbot.Super.call(this, vis, $el, uiState); this._createVis(); } @@ -22,7 +22,7 @@ module.exports = function VislibRenderbotFactory(Private) { self.vislibVis.on(event, listener); }); - if (this.chartData) self.vislibVis.render(this.chartData); + if (this.chartData) self.vislibVis.render(this.chartData, this.uiState); }; VislibRenderbot.prototype._getVislibParams = function () { @@ -43,7 +43,7 @@ module.exports = function VislibRenderbotFactory(Private) { VislibRenderbot.prototype.buildChartData = buildChartData; VislibRenderbot.prototype.render = function (esResponse) { this.chartData = this.buildChartData(esResponse); - this.vislibVis.render(this.chartData); + this.vislibVis.render(this.chartData, this.uiState); }; VislibRenderbot.prototype.destroy = function () { diff --git a/src/ui/public/vislib_vis_type/VislibVisType.js b/src/ui/public/vislib_vis_type/VislibVisType.js index 32dbb8503d99d..91890c1591cd5 100644 --- a/src/ui/public/vislib_vis_type/VislibVisType.js +++ b/src/ui/public/vislib_vis_type/VislibVisType.js @@ -25,8 +25,8 @@ define(function (require) { this.listeners = opts.listeners || {}; } - VislibVisType.prototype.createRenderbot = function (vis, $el) { - return new VislibRenderbot(vis, $el); + VislibVisType.prototype.createRenderbot = function (vis, $el, uiState) { + return new VislibRenderbot(vis, $el, uiState); }; return VislibVisType; diff --git a/src/ui/public/vislib_vis_type/__tests__/_VislibRenderbot.js b/src/ui/public/vislib_vis_type/__tests__/_VislibRenderbot.js index 7e612c815d968..8b0f94f6966d6 100644 --- a/src/ui/public/vislib_vis_type/__tests__/_VislibRenderbot.js +++ b/src/ui/public/vislib_vis_type/__tests__/_VislibRenderbot.js @@ -8,6 +8,7 @@ describe('renderbot', function exportWrapper() { var Vis; var Renderbot; var VislibRenderbot; + var persistedState; var normalizeChartData; var mockVisType = { name: 'test' @@ -21,6 +22,7 @@ describe('renderbot', function exportWrapper() { Vis = Private(require('ui/vislib/vis')); Renderbot = Private(require('ui/Vis/Renderbot')); VislibRenderbot = Private(require('ui/vislib_vis_type/VislibRenderbot')); + persistedState = new (Private(require('ui/persisted_state/persisted_state')))(); normalizeChartData = Private(require('ui/agg_response/index')); }); } @@ -35,7 +37,7 @@ describe('renderbot', function exportWrapper() { beforeEach(function () { createVisStub = sinon.stub(VislibRenderbot.prototype, '_createVis', _.noop); - renderbot = new VislibRenderbot(vis, $el); + renderbot = new VislibRenderbot(vis, $el, persistedState); }); it('should be a Renderbot', function () { @@ -63,7 +65,7 @@ describe('renderbot', function exportWrapper() { beforeEach(function () { sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _.constant({})); listenerSpy = sinon.spy(vislib.Vis.prototype, 'on'); - renderbot = new VislibRenderbot(vis, $el); + renderbot = new VislibRenderbot(vis, $el, persistedState); }); it('should attach listeners and set vislibVis', function () { @@ -91,7 +93,7 @@ describe('renderbot', function exportWrapper() { createVisSpy = sinon.spy(VislibRenderbot.prototype, '_createVis'); // getParamsStub = sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _identity); // getParamsStub.returns(params); - renderbot = new VislibRenderbot(vis, $el); + renderbot = new VislibRenderbot(vis, $el, persistedState); }); it('should create a new Vis object when params change', function () { @@ -120,13 +122,13 @@ describe('renderbot', function exportWrapper() { }); it('should use #buildChartData', function () { - var renderbot = new VislibRenderbot(vis, $el); + var renderbot = new VislibRenderbot(vis, $el, persistedState); var football = {}; var buildStub = sinon.stub(renderbot, 'buildChartData', _.constant(football)); var renderStub = sinon.stub(renderbot.vislibVis, 'render'); - renderbot.render('flat data'); + renderbot.render('flat data', persistedState); expect(renderStub.callCount).to.be(1); expect(buildStub.callCount).to.be(1); expect(renderStub.firstCall.args[0]).to.be(football); @@ -149,7 +151,7 @@ describe('renderbot', function exportWrapper() { beforeEach(function () { sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _.constant({})); listenerSpy = sinon.spy(vislib.Vis.prototype, 'off'); - renderbot = new VislibRenderbot(vis, $el); + renderbot = new VislibRenderbot(vis, $el, persistedState); }); it('should detatch listeners', function () { diff --git a/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js b/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js index a623a83d08e9a..99b28d642cfe5 100644 --- a/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js +++ b/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js @@ -4,7 +4,6 @@ describe('renderbot#buildChartData', function () { var expect = require('expect.js'); var sinon = require('auto-release-sinon'); - var VislibRenderbot; var buildChartData; var aggResponse; var TableGroup; @@ -15,7 +14,6 @@ describe('renderbot#buildChartData', function () { Table = Private(require('ui/agg_response/tabify/_table')); TableGroup = Private(require('ui/agg_response/tabify/_table_group')); aggResponse = Private(require('ui/agg_response/index')); - VislibRenderbot = Private(require('ui/vislib_vis_type/VislibRenderbot')); buildChartData = Private(require('ui/vislib_vis_type/buildChartData')); })); diff --git a/src/ui/public/visualize/visualize.html b/src/ui/public/visualize/visualize.html index 6b105e5c8fab9..3d63d0b84a846 100644 --- a/src/ui/public/visualize/visualize.html +++ b/src/ui/public/visualize/visualize.html @@ -8,8 +8,11 @@

No results found

+ class="vis-container" > +
+ + diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index 10d9b0dfd01a1..bc9224fb6783b 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -1,10 +1,12 @@ define(function (require) { require('ui/modules') .get('kibana/directive') - .directive('visualize', function (Notifier, SavedVis, indexPatterns, Private, config) { + .directive('visualize', function (Notifier, SavedVis, indexPatterns, Private, config, $timeout) { require('ui/visualize/spy'); require('ui/visualize/visualize.less'); + require('ui/visualize/visualize_legend'); + var $ = require('jquery'); var _ = require('lodash'); var visTypes = Private(require('ui/registry/vis_types')); @@ -40,26 +42,25 @@ define(function (require) { } var getVisEl = getter('.visualize-chart'); - var getSpyEl = getter('visualize-spy'); + var getVisContainer = getter('.vis-container'); $scope.fullScreenSpy = false; $scope.spy = {}; $scope.spy.mode = ($scope.uiState) ? $scope.uiState.get('spy.mode', {}) : {}; var applyClassNames = function () { - var $spyEl = getSpyEl(); - var $visEl = getVisEl(); + var $visEl = getVisContainer(); var fullSpy = ($scope.spy.mode && ($scope.spy.mode.fill || $scope.fullScreenSpy)); - // external - $el.toggleClass('only-visualization', !$scope.spy.mode); - $el.toggleClass('visualization-and-spy', $scope.spy.mode && !fullSpy); - $el.toggleClass('only-spy', Boolean(fullSpy)); - if ($spyEl) $spyEl.toggleClass('only', Boolean(fullSpy)); - - // internal - $visEl.toggleClass('spy-visible', Boolean($scope.spy.mode)); $visEl.toggleClass('spy-only', Boolean(fullSpy)); + + // Basically a magic number, chart must be at least this big or only the spy will show + var visTooSmall = 100; + $timeout(function () { + if ($visEl.height() < visTooSmall) { + $visEl.addClass('spy-only'); + }; + }, 0); }; // we need to wait for some watchers to fire at least once @@ -113,7 +114,7 @@ define(function (require) { } if (oldVis) $scope.renderbot = null; - if (vis) $scope.renderbot = vis.type.createRenderbot(vis, $visEl); + if (vis) $scope.renderbot = vis.type.createRenderbot(vis, $visEl, $scope.uiState); })); $scope.$watchCollection('vis.params', prereq(function () { diff --git a/src/ui/public/visualize/visualize.less b/src/ui/public/visualize/visualize.less index 6290045efcdcc..6879cf173d088 100644 --- a/src/ui/public/visualize/visualize.less +++ b/src/ui/public/visualize/visualize.less @@ -12,20 +12,20 @@ visualize { white-space: pre-line; } - .visualize-chart { + .vis-container { + height: 1px; + display: flex; + flex-direction: row; + flex: 1 1 auto; overflow: auto; -webkit-transition: opacity 0.01s; transition: opacity 0.01s; - &.spy-visible { - margin-bottom: 10px; - height: 0px; - } - &.spy-only { display: none; } + } .loading { @@ -181,3 +181,4 @@ visualize-spy { white-space: pre-wrap; } } + diff --git a/src/ui/public/visualize/visualize_legend.html b/src/ui/public/visualize/visualize_legend.html new file mode 100644 index 0000000000000..4abb1493e4a97 --- /dev/null +++ b/src/ui/public/visualize/visualize_legend.html @@ -0,0 +1,41 @@ +
+
+ +
+ +
\ No newline at end of file diff --git a/src/ui/public/visualize/visualize_legend.js b/src/ui/public/visualize/visualize_legend.js new file mode 100644 index 0000000000000..a3c60e9c5f86f --- /dev/null +++ b/src/ui/public/visualize/visualize_legend.js @@ -0,0 +1,103 @@ +define(function (require) { + var _ = require('lodash'); + var html = require('ui/visualize/visualize_legend.html'); + + var $ = require('jquery'); + var d3 = require('d3'); + var findByParam = require('ui/utils/find_by_param'); + + require('ui/modules').get('kibana') + .directive('visualizeLegend', function (Private, getAppState) { + var Data = Private(require('ui/vislib/lib/data')); + var colorPalette = Private(require('ui/vislib/components/color/color')); + var filterBarClickHandler = Private(require('ui/filter_bar/filter_bar_click_handler')); + + return { + restrict: 'E', + template: html, + link: function ($scope, $elem) { + var $state = getAppState(); + var clickHandler = filterBarClickHandler($state); + $scope.open = $scope.uiState.get('vis.legendOpen', true); + + $scope.$watch('renderbot.chartData', function (data) { + if (!data) return; + $scope.data = data; + refresh(); + }); + + $scope.highlightSeries = function (label) { + $('[data-label]', $elem.siblings()).not('[data-label="' + label + '"]').css('opacity', 0.5); + }; + + $scope.unhighlightSeries = function () { + $('[data-label]', $elem.siblings()).css('opacity', 1); + }; + + $scope.setColor = function (label, color) { + var colors = $scope.uiState.get('vis.colors') || {}; + colors[label] = color; + $scope.uiState.set('vis.colors', colors); + refresh(); + }; + + $scope.toggleLegend = function () { + var bwcAddLegend = $scope.renderbot.vislibVis._attr.addLegend; + var bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; + $scope.open = !$scope.uiState.get('vis.legendOpen', bwcLegendStateDefault); + $scope.uiState.set('vis.legendOpen', $scope.open); + }; + + $scope.filter = function (legendData, negate) { + clickHandler({point: legendData, negate: negate}); + }; + + $scope.canFilter = function (legendData) { + var filters = clickHandler({point: legendData}, true) || []; + return filters.length; + }; + + $scope.colors = [ + '#3F6833', '#967302', '#2F575E', '#99440A', '#58140C', '#052B51', '#511749', '#3F2B5B', //6 + '#508642', '#CCA300', '#447EBC', '#C15C17', '#890F02', '#0A437C', '#6D1F62', '#584477', //2 + '#629E51', '#E5AC0E', '#64B0C8', '#E0752D', '#BF1B00', '#0A50A1', '#962D82', '#614D93', //4 + '#7EB26D', '#EAB839', '#6ED0E0', '#EF843C', '#E24D42', '#1F78C1', '#BA43A9', '#705DA0', // Normal + '#9AC48A', '#F2C96D', '#65C5DB', '#F9934E', '#EA6460', '#5195CE', '#D683CE', '#806EB7', //5 + '#B7DBAB', '#F4D598', '#70DBED', '#F9BA8F', '#F29191', '#82B5D8', '#E5A8E2', '#AEA2E0', //3 + '#E0F9D7', '#FCEACA', '#CFFAFF', '#F9E2D2', '#FCE2DE', '#BADFF4', '#F9D9F9', '#DEDAF7' //7 + ]; + + function refresh() { + var vislibVis = $scope.renderbot.vislibVis; + + if ($scope.uiState.get('vis.legendOpen') == null && vislibVis._attr.addLegend != null) { + $scope.open = vislibVis._attr.addLegend; + } + + $scope.labels = getLabels($scope.data, vislibVis._attr.type); + $scope.getColor = colorPalette(_.pluck($scope.labels, 'label'), $scope.uiState.get('vis.colors')); + } + + // Most of these functions were moved directly from the old Legend class. Not a fan of this. + function getLabels(data, type) { + if (!data) return []; + var data = data.columns || data.rows || [data]; + if (type === 'pie') return Data.prototype.pieNames(data); + return getSeriesLabels(data); + }; + + function getSeriesLabels(data) { + var values = data.map(function (chart) { + return chart.series; + }) + .reduce(function (a, b) { + return a.concat(b); + }, []); + return _.compact(_.uniq(values, 'label')); + } + + + } + }; + }); +});