diff --git a/src/ui/public/vislib/__tests__/components/color.js b/src/ui/public/vislib/__tests__/components/color.js index 19138ad8ab9cf..b04fbb2cc343d 100644 --- a/src/ui/public/vislib/__tests__/components/color.js +++ b/src/ui/public/vislib/__tests__/components/color.js @@ -16,14 +16,14 @@ describe('Vislib Color Module Test Suite', function () { describe('Color (main)', function () { let previousConfig; let getColors; - let arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest']; - let arrayOfNumbers = [1, 2, 3, 4, 5]; - let arrayOfUndefinedValues = [undefined, undefined, undefined]; - let arrayOfObjects = [{}, {}, {}]; - let arrayOfBooleans = [true, false, true]; - let arrayOfNullValues = [null, null, null]; - let emptyObject = {}; - let nullValue = null; + const arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest']; + const arrayOfNumbers = [1, 2, 3, 4, 5]; + const arrayOfUndefinedValues = [undefined, undefined, undefined]; + const arrayOfObjects = [{}, {}, {}]; + const arrayOfBooleans = [true, false, true]; + const arrayOfNullValues = [null, null, null]; + const emptyObject = {}; + const nullValue = null; let notAValue; let color; @@ -243,14 +243,14 @@ describe('Vislib Color Module Test Suite', function () { }); describe('Color Palette', function () { - let num1 = 45; - let num2 = 72; - let num3 = 90; - let string = 'Welcome'; - let bool = true; - let nullValue = null; - let emptyArr = []; - let emptyObject = {}; + const num1 = 45; + const num2 = 72; + const num3 = 90; + const string = 'Welcome'; + const bool = true; + const nullValue = null; + const emptyArr = []; + const emptyObject = {}; let notAValue; let createColorPalette; let colorPalette; diff --git a/src/ui/public/vislib/__tests__/components/labels.js b/src/ui/public/vislib/__tests__/components/labels.js index f0d7cd5e31c8a..dfbaacdbe0625 100644 --- a/src/ui/public/vislib/__tests__/components/labels.js +++ b/src/ui/public/vislib/__tests__/components/labels.js @@ -15,7 +15,7 @@ let rowsArr; let uniqLabels; let error; -let seriesData = { +const seriesData = { 'label': '', 'series': [ { @@ -25,7 +25,7 @@ let seriesData = { ] }; -let rowsData = { +const rowsData = { 'rows': [ { 'label': 'a', @@ -66,7 +66,7 @@ let rowsData = { ] }; -let columnsData = { +const columnsData = { 'columns': [ { 'label': 'a', @@ -152,23 +152,23 @@ describe('Vislib Labels Module Test Suite', function () { }); describe('Data array', function () { - let childrenObject = { + const childrenObject = { children: [] }; - let seriesObject = { + const seriesObject = { series: [] }; - let rowsObject = { + const rowsObject = { rows: [] }; - let columnsObject = { + const columnsObject = { columns: [] }; - let string = 'string'; - let number = 23; - let boolean = false; - let emptyArray = []; - let nullValue = null; + const string = 'string'; + const number = 23; + const boolean = false; + const emptyArray = []; + const nullValue = null; let notAValue; let dataArray; let testSeries; @@ -267,7 +267,7 @@ describe('Vislib Labels Module Test Suite', function () { describe('Unique labels', function () { let uniqLabels; - let arrObj = [ + const arrObj = [ {'label': 'a'}, {'label': 'b'}, {'label': 'b'}, @@ -276,12 +276,12 @@ describe('Vislib Labels Module Test Suite', function () { {'label': 'd'}, {'label': 'f'} ]; - let string = 'string'; - let number = 24; - let boolean = false; - let nullValue = null; - let emptyObject = {}; - let emptyArray = []; + const string = 'string'; + const number = 24; + const boolean = false; + const nullValue = null; + const emptyObject = {}; + const emptyArray = []; let notAValue; let uniq; let testArr; @@ -340,18 +340,18 @@ describe('Vislib Labels Module Test Suite', function () { }); describe('Get series', function () { - let string = 'string'; - let number = 24; - let boolean = false; - let nullValue = null; - let rowsObject = { + const string = 'string'; + const number = 24; + const boolean = false; + const nullValue = null; + const rowsObject = { rows: [] }; - let columnsObject = { + const columnsObject = { columns: [] }; - let emptyObject = {}; - let emptyArray = []; + const emptyObject = {}; + const emptyArray = []; let notAValue; let getSeries; let columnsLabels; diff --git a/src/ui/public/vislib/__tests__/components/zero_injection.js b/src/ui/public/vislib/__tests__/components/zero_injection.js index 2393226c1f855..0249c6a6b5a94 100644 --- a/src/ui/public/vislib/__tests__/components/zero_injection.js +++ b/src/ui/public/vislib/__tests__/components/zero_injection.js @@ -11,7 +11,7 @@ import VislibComponentsZeroInjectionZeroFilledArrayProvider from 'ui/vislib/comp import VislibComponentsZeroInjectionZeroFillDataArrayProvider from 'ui/vislib/components/zero_injection/zero_fill_data_array'; describe('Vislib Zero Injection Module Test Suite', function () { - let dateHistogramRows = { + const dateHistogramRows = { 'rows': [ { 'label': 'Top 5 @tags: success', @@ -161,7 +161,7 @@ describe('Vislib Zero Injection Module Test Suite', function () { ] }; - let seriesData = { + const seriesData = { series: [ { label: '200', @@ -176,7 +176,7 @@ describe('Vislib Zero Injection Module Test Suite', function () { ] }; - let multiSeriesData = { + const multiSeriesData = { series: [ { label: '200', @@ -205,7 +205,7 @@ describe('Vislib Zero Injection Module Test Suite', function () { ] }; - let multiSeriesNumberedData = { + const multiSeriesNumberedData = { series: [ { label: '200', @@ -234,24 +234,24 @@ describe('Vislib Zero Injection Module Test Suite', function () { ] }; - let childrenObject = { + const childrenObject = { children: [] }; - let seriesObject = { + const seriesObject = { series: [] }; - let rowsObject = { + const rowsObject = { rows: [] }; - let columnsObject = { + const columnsObject = { columns: [] }; - let emptyObject = {}; - let str = 'string'; - let number = 24; - let boolean = false; - let nullValue = null; - let emptyArray = []; + const emptyObject = {}; + const str = 'string'; + const number = 24; + const boolean = false; + const nullValue = null; + const emptyArray = []; let notAValue; describe('Zero Injection (main)', function () { @@ -497,8 +497,8 @@ describe('Vislib Zero Injection Module Test Suite', function () { describe('Zero Filled Array', function () { let createZeroArray; - let arr1 = [1, 2, 3, 4, 5]; - let arr2 = ['1', '2', '3', '4', '5']; + const arr1 = [1, 2, 3, 4, 5]; + const arr2 = ['1', '2', '3', '4', '5']; let results1; let results2; @@ -578,10 +578,10 @@ describe('Vislib Zero Injection Module Test Suite', function () { describe('Zero Filled Data Array', function () { let zeroFillArray; - let xValueArr = [1, 2, 3, 4, 5]; + const xValueArr = [1, 2, 3, 4, 5]; let createZeroArray; let arr1; - let arr2 = [ {x: 3, y: 834} ]; + const arr2 = [ {x: 3, y: 834} ]; let results; beforeEach(ngMock.module('kibana')); @@ -659,7 +659,7 @@ describe('Vislib Zero Injection Module Test Suite', function () { }); it('should return ordered x values', function () { - let values = results.rows[0].series[0].values; + const values = results.rows[0].series[0].values; expect(values[0].x).to.be.lessThan(values[1].x); expect(values[1].x).to.be.lessThan(values[2].x); expect(values[2].x).to.be.lessThan(values[3].x); diff --git a/src/ui/public/vislib/__tests__/lib/axis_title.js b/src/ui/public/vislib/__tests__/lib/axis_title.js index bdb7f08b0701d..d75b3d1028b5a 100644 --- a/src/ui/public/vislib/__tests__/lib/axis_title.js +++ b/src/ui/public/vislib/__tests__/lib/axis_title.js @@ -17,7 +17,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { let dataObj; let xTitle; let yTitle; - let data = { + const data = { hits: 621, label: '', ordered: { diff --git a/src/ui/public/vislib/__tests__/lib/chart_title.js b/src/ui/public/vislib/__tests__/lib/chart_title.js index 469949d6d777b..ddb4c6ee0a4e8 100644 --- a/src/ui/public/vislib/__tests__/lib/chart_title.js +++ b/src/ui/public/vislib/__tests__/lib/chart_title.js @@ -15,7 +15,7 @@ describe('Vislib ChartTitle Class Test Suite', function () { let chartTitle; let el; let dataObj; - let data = { + const data = { hits: 621, label: '', ordered: { diff --git a/src/ui/public/vislib/__tests__/lib/data.js b/src/ui/public/vislib/__tests__/lib/data.js index 20224d660e6d3..bb6890a60b112 100644 --- a/src/ui/public/vislib/__tests__/lib/data.js +++ b/src/ui/public/vislib/__tests__/lib/data.js @@ -9,7 +9,7 @@ import dataStacked from 'fixtures/vislib/mock_data/stacked/_stacked'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -let seriesData = { +const seriesData = { 'label': '', 'series': [ { @@ -19,7 +19,7 @@ let seriesData = { ] }; -let rowsData = { +const rowsData = { 'rows': [ { 'label': 'a', @@ -60,7 +60,7 @@ let rowsData = { ] }; -let colsData = { +const colsData = { 'columns': [ { 'label': 'a', @@ -117,12 +117,12 @@ describe('Vislib Data Class Test Suite', function () { }); it('should return an object', function () { - let rowIn = new Data(rowsData, {}, persistedState); + const rowIn = new Data(rowsData, {}, persistedState); expect(_.isObject(rowIn)).to.be(true); }); it('should update label in series data', function () { - let seriesDataWithoutLabelInSeries = { + const seriesDataWithoutLabelInSeries = { 'label': '', 'series': [ { @@ -132,12 +132,12 @@ describe('Vislib Data Class Test Suite', function () { ], 'yAxisLabel': 'customLabel' }; - let modifiedData = new Data(seriesDataWithoutLabelInSeries, {}, persistedState); + const modifiedData = new Data(seriesDataWithoutLabelInSeries, {}, persistedState); expect(modifiedData.data.series[0].label).to.be('customLabel'); }); it('should update label in row data', function () { - let seriesDataWithoutLabelInRow = { + const seriesDataWithoutLabelInRow = { 'rows': [ { 'label': '', @@ -162,13 +162,13 @@ describe('Vislib Data Class Test Suite', function () { ], }; - let modifiedData = new Data(seriesDataWithoutLabelInRow, {}, persistedState); + const 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'); }); it('should update label in column data', function () { - let seriesDataWithoutLabelInRow = { + const seriesDataWithoutLabelInRow = { 'columns': [ { 'label': '', @@ -194,7 +194,7 @@ describe('Vislib Data Class Test Suite', function () { 'yAxisLabel': 'customLabel' }; - let modifiedData = new Data(seriesDataWithoutLabelInRow, {}, persistedState); + const 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'); }); @@ -203,7 +203,7 @@ describe('Vislib Data Class Test Suite', function () { describe('_removeZeroSlices', function () { let data; - let pieData = { + const pieData = { slices: { children: [ {size: 30}, @@ -218,7 +218,7 @@ describe('Vislib Data Class Test Suite', function () { }); it('should remove zero values', function () { - let slices = data._removeZeroSlices(data.data.slices); + const slices = data._removeZeroSlices(data.data.slices); expect(slices.children.length).to.be(2); }); }); @@ -250,8 +250,8 @@ describe('Vislib Data Class Test Suite', function () { function testLength(inputData) { return function () { - let data = new Data(inputData, {}, persistedState); - let len = _.reduce(data.chartData(), function (sum, chart) { + const data = new Data(inputData, {}, persistedState); + const len = _.reduce(data.chartData(), function (sum, chart) { return sum + chart.series.reduce(function (sum, series) { return sum + series.values.length; }, 0); @@ -266,9 +266,9 @@ describe('Vislib Data Class Test Suite', function () { let visData; let visDataNeg; let visDataStacked; - let minValue = 4; - let minValueNeg = -41; - let minValueStacked = 15; + const minValue = 4; + const minValueNeg = -41; + const minValueStacked = 15; beforeEach(function () { visData = new Data(dataSeries, {}, persistedState); @@ -286,15 +286,15 @@ describe('Vislib Data Class Test Suite', function () { }); it('should have a minimum date value that is greater than the max value within the date range', function () { - let series = _.pluck(visData.chartData(), 'series'); - let stackedSeries = _.pluck(visDataStacked.chartData(), 'series'); + const series = _.pluck(visData.chartData(), 'series'); + const stackedSeries = _.pluck(visDataStacked.chartData(), 'series'); expect(_.min(series.values, function (d) { return d.x; })).to.be.greaterThan(minValue); expect(_.min(stackedSeries.values, function (d) { return d.x; })).to.be.greaterThan(minValueStacked); }); it('allows passing a value getter for manipulating the values considered', function () { - let realMin = visData.getYMin(); - let multiplier = 13.2; + const realMin = visData.getYMin(); + const multiplier = 13.2; expect(visData.getYMin(function (d) { return d.y * multiplier; })).to.be(realMin * multiplier); }); }); @@ -303,9 +303,9 @@ describe('Vislib Data Class Test Suite', function () { let visData; let visDataNeg; let visDataStacked; - let maxValue = 41; - let maxValueNeg = -4; - let maxValueStacked = 115; + const maxValue = 41; + const maxValueNeg = -4; + const maxValueStacked = 115; beforeEach(function () { visData = new Data(dataSeries, {}, persistedState); @@ -323,22 +323,22 @@ describe('Vislib Data Class Test Suite', function () { }); it('should have a minimum date value that is greater than the max value within the date range', function () { - let series = _.pluck(visData.chartData(), 'series'); - let stackedSeries = _.pluck(visDataStacked.chartData(), 'series'); + const series = _.pluck(visData.chartData(), 'series'); + const stackedSeries = _.pluck(visDataStacked.chartData(), 'series'); expect(_.min(series, function (d) { return d.x; })).to.be.greaterThan(maxValue); expect(_.min(stackedSeries, function (d) { return d.x; })).to.be.greaterThan(maxValueStacked); }); it('allows passing a value getter for manipulating the values considered', function () { - let realMax = visData.getYMax(); - let multiplier = 13.2; + const realMax = visData.getYMax(); + const multiplier = 13.2; expect(visData.getYMax(function (d) { return d.y * multiplier; })).to.be(realMax * multiplier); }); }); describe('geohashGrid methods', function () { let data; - let geohashGridData = { + const geohashGridData = { hits: 3954, rows: [{ title: 'Top 5 _type: apache', @@ -381,14 +381,14 @@ describe('Vislib Data Class Test Suite', function () { describe('getVisData', function () { it('should return the rows property', function () { - let visData = data.getVisData(); + const visData = data.getVisData(); expect(visData).to.eql(geohashGridData.rows); }); }); describe('getGeoExtents', function () { it('should return the min and max geoJson properties', function () { - let minMax = data.getGeoExtents(); + const minMax = data.getGeoExtents(); expect(minMax.min).to.be(1); expect(minMax.max).to.be(331); }); @@ -397,12 +397,12 @@ describe('Vislib Data Class Test Suite', function () { describe('null value check', function () { it('should return false', function () { - let data = new Data(rowsData, {}, persistedState); + const data = new Data(rowsData, {}, persistedState); expect(data.hasNullValues()).to.be(false); }); it('should return true', function () { - let nullRowData = { rows: rowsData.rows.slice(0) }; + const nullRowData = { rows: rowsData.rows.slice(0) }; nullRowData.rows.push({ 'label': 'e', 'series': [ @@ -413,7 +413,7 @@ describe('Vislib Data Class Test Suite', function () { ] }); - let data = new Data(nullRowData, {}, persistedState); + const 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 2fedc6a239ca2..f0bf50c408c66 100644 --- a/src/ui/public/vislib/__tests__/lib/dispatch.js +++ b/src/ui/public/vislib/__tests__/lib/dispatch.js @@ -39,7 +39,7 @@ describe('Vislib Dispatch Class Test Suite', function () { }); it('extends the SimpleEmitter class', function () { - let events = _.pluck(vis.handler.charts, 'events'); + const events = _.pluck(vis.handler.charts, 'events'); expect(events.length).to.be.above(0); events.forEach(function (dispatch) { expect(dispatch).to.be.a(SimpleEmitter); @@ -65,11 +65,11 @@ describe('Vislib Dispatch Class Test Suite', function () { describe('addEvent method', function () { it('returns a function that binds the passed event to a selection', function () { - let chart = _.first(vis.handler.charts); - let apply = chart.events.addEvent('event', _.noop); + const chart = _.first(vis.handler.charts); + const apply = chart.events.addEvent('event', _.noop); expect(apply).to.be.a('function'); - let els = getEls(vis.el, 3, 'div'); + const els = getEls(vis.el, 3, 'div'); apply(els); els.each(function () { expect(d3.select(this).on('event')).to.be(_.noop); @@ -88,11 +88,11 @@ describe('Vislib Dispatch Class Test Suite', function () { }); it('returns a function that binds ' + event + ' events to a selection', function () { - let chart = _.first(vis.handler.charts); - let apply = chart.events[name](d3.select(document.createElement('svg'))); + const chart = _.first(vis.handler.charts); + const apply = chart.events[name](d3.select(document.createElement('svg'))); expect(apply).to.be.a('function'); - let els = getEls(vis.el, 3, 'div'); + const els = getEls(vis.el, 3, 'div'); apply(els); els.each(function () { expect(d3.select(this).on(event)).to.be.a('function'); @@ -109,7 +109,7 @@ describe('Vislib Dispatch Class Test Suite', function () { describe('addMousePointer method', function () { it('should be a function', function () { vis.handler.charts.forEach(function (chart) { - let pointer = chart.events.addMousePointer; + const pointer = chart.events.addMousePointer; expect(_.isFunction(pointer)).to.be(true); }); @@ -121,7 +121,6 @@ describe('Vislib Dispatch Class Test Suite', function () { it('should attach whatever gets passed on vis.on() to chart.events', function (done) { let vis; let persistedState; - let chart; ngMock.module('kibana'); ngMock.inject(function (Private) { vis = Private(FixturesVislibVisFixtureProvider)(); @@ -141,7 +140,6 @@ describe('Vislib Dispatch Class Test Suite', function () { it('can be added after rendering', function () { let vis; let persistedState; - let chart; ngMock.module('kibana'); ngMock.inject(function (Private) { vis = Private(FixturesVislibVisFixtureProvider)(); diff --git a/src/ui/public/vislib/__tests__/lib/handler/handler.js b/src/ui/public/vislib/__tests__/lib/handler/handler.js index 6953e3ebef2e0..2e9e74fba7d5d 100644 --- a/src/ui/public/vislib/__tests__/lib/handler/handler.js +++ b/src/ui/public/vislib/__tests__/lib/handler/handler.js @@ -1,5 +1,3 @@ - -import angular from 'angular'; import ngMock from 'ng_mock'; import expect from 'expect.js'; @@ -12,13 +10,13 @@ import $ from 'jquery'; import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -let dateHistogramArray = [ +const dateHistogramArray = [ series, columns, rows, stackedSeries ]; -let names = [ +const names = [ 'series', 'columns', 'rows', @@ -30,7 +28,7 @@ dateHistogramArray.forEach(function (data, i) { let Handler; let vis; let persistedState; - let events = [ + const events = [ 'click', 'brush' ]; diff --git a/src/ui/public/vislib/__tests__/lib/layout/layout.js b/src/ui/public/vislib/__tests__/lib/layout/layout.js index a69e53aa89235..58179fbb1efdc 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/layout.js +++ b/src/ui/public/vislib/__tests__/lib/layout/layout.js @@ -12,13 +12,13 @@ import $ from 'jquery'; import VislibLibLayoutLayoutProvider from 'ui/vislib/lib/layout/layout'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -let dateHistogramArray = [ +const dateHistogramArray = [ series, columns, rows, stackedSeries ]; -let names = [ +const names = [ 'series', 'columns', 'rows', diff --git a/src/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js b/src/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js index ce07621a41d93..dac0111531d9b 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js +++ b/src/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js @@ -15,7 +15,7 @@ describe('Vislib Split Function Test Suite', function () { let xAxisSplit; let yAxisSplit; let el; - let data = { + const data = { rows: [ { hits : 621, diff --git a/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js b/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js index f500ad8dcb534..6ac42cbe9b52f 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js +++ b/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js @@ -9,7 +9,7 @@ describe('Vislib Column Layout Test Suite', function () { let layoutType; let columnLayout; let el; - let data = { + const data = { hits: 621, label: '', ordered: { diff --git a/src/ui/public/vislib/__tests__/lib/resize_checker.js b/src/ui/public/vislib/__tests__/lib/resize_checker.js index f4bdb6b70622b..bafaef16eaf2d 100644 --- a/src/ui/public/vislib/__tests__/lib/resize_checker.js +++ b/src/ui/public/vislib/__tests__/lib/resize_checker.js @@ -16,7 +16,7 @@ describe('Vislib Resize Checker', function () { let EventEmitter; let checker; let reflowWatcher; - let reflowSpies = {}; + const reflowSpies = {}; beforeEach(ngMock.module('kibana')); @@ -27,7 +27,7 @@ describe('Vislib Resize Checker', function () { reflowSpies.on = sinon.spy(reflowWatcher, 'on'); reflowSpies.off = sinon.spy(reflowWatcher, 'off'); - let $el = $(document.createElement('div')) + const $el = $(document.createElement('div')) .appendTo('body') .css('visibility', 'hidden') .get(0); @@ -47,7 +47,7 @@ describe('Vislib Resize Checker', function () { it('listens for the "reflow" event of the reflowWatchers', function () { expect(reflowSpies.on).to.have.property('callCount', 1); - let call = reflowSpies.on.getCall(0); + const call = reflowSpies.on.getCall(0); expect(call.args[0]).to.be('reflow'); }); @@ -63,8 +63,8 @@ describe('Vislib Resize Checker', function () { describe('#read', function () { it('gets the proper dimensions for the element', function () { - let dimensions = checker.read(); - let windowWidth = document.documentElement.clientWidth; + const dimensions = checker.read(); + const windowWidth = document.documentElement.clientWidth; expect(dimensions.w).to.equal(windowWidth); expect(dimensions.h).to.equal(0); @@ -73,7 +73,7 @@ describe('Vislib Resize Checker', function () { describe('#saveSize', function () { it('calls #read() when no arg is passed', function () { - let stub = sinon.stub(checker, 'read').returns({}); + const stub = sinon.stub(checker, 'read').returns({}); checker.saveSize(); @@ -81,7 +81,7 @@ describe('Vislib Resize Checker', function () { }); it('saves the size of the element', function () { - let football = {}; + const football = {}; checker.saveSize(football); expect(checker).to.have.property('_savedSize', football); }); @@ -123,12 +123,12 @@ describe('Vislib Resize Checker', function () { }); it('emits "resize" based on MS_MAX_RESIZE_DELAY, even if el\'s constantly changing size', function () { - let steps = _.random(5, 10); + const steps = _.random(5, 10); this.slow(steps * 10); // we are going to fake the delay using the fake clock - let msStep = Math.floor(ResizeChecker.MS_MAX_RESIZE_DELAY / (steps - 1)); - let clock = sinon.useFakeTimers(); + const msStep = Math.floor(ResizeChecker.MS_MAX_RESIZE_DELAY / (steps - 1)); + const clock = sinon.useFakeTimers(); _.times(steps, function step(i) { checker.$el.css('height', 100 + i); @@ -145,8 +145,8 @@ describe('Vislib Resize Checker', function () { describe('#destroy()', function () { it('removes the "reflow" event from the reflowWatcher', function () { - let onCall = reflowSpies.on.getCall(0); - let handler = onCall.args[1]; + const onCall = reflowSpies.on.getCall(0); + const handler = onCall.args[1]; checker.destroy(); expect(reflowSpies.off).to.have.property('callCount', 1); @@ -154,7 +154,7 @@ describe('Vislib Resize Checker', function () { }); it('clears the timeout', function () { - let spy = sinon.spy(window, 'clearTimeout'); + const spy = sinon.spy(window, 'clearTimeout'); checker.destroy(); expect(spy).to.have.property('callCount', 1); }); @@ -193,9 +193,9 @@ describe('Vislib Resize Checker', function () { timerId = checker.continueSchedule(); } - let last = _.last(schedule); + const last = _.last(schedule); _.times(5, function () { - let timer = clock.timers[checker.continueSchedule()]; + const timer = clock.timers[checker.continueSchedule()]; expect(timer).to.have.property('callAt', last); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/x_axis.js b/src/ui/public/vislib/__tests__/lib/x_axis.js index 7b9ea1a5b3e03..d42e135477109 100644 --- a/src/ui/public/vislib/__tests__/lib/x_axis.js +++ b/src/ui/public/vislib/__tests__/lib/x_axis.js @@ -16,7 +16,7 @@ describe('Vislib xAxis Class Test Suite', function () { let el; let fixture; let dataObj; - let data = { + const data = { hits: 621, label: '', ordered: { diff --git a/src/ui/public/vislib/__tests__/lib/y_axis.js b/src/ui/public/vislib/__tests__/lib/y_axis.js index d89c1febb3003..19e5ab0883146 100644 --- a/src/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/ui/public/vislib/__tests__/lib/y_axis.js @@ -15,7 +15,7 @@ let buildYAxis; let yAxis; let yAxisDiv; -let timeSeries = [ +const timeSeries = [ 1408734060000, 1408734090000, 1408734120000, @@ -28,7 +28,7 @@ let timeSeries = [ 1408734330000 ]; -let defaultGraphData = [ +const defaultGraphData = [ [ 8, 23, 30, 28, 36, 30, 26, 22, 29, 24 ], [ 2, 13, 20, 18, 26, 20, 16, 12, 19, 14 ] ]; @@ -43,7 +43,7 @@ function makeSeriesData(data) { } function createData(seriesData) { - let data = { + const data = { hits: 621, label: 'test', ordered: { @@ -59,7 +59,7 @@ function createData(seriesData) { yAxisLabel: 'Count' }; - let node = $('
').addClass('vis-alerts-text').text(msg); + + return $('
').addClass('vis-alerts-text').text(msg); - - return $('
' + _.escape(d.label) + '
'; + }); } - this.el = el; - this.tooltip = new Tooltip('chart-title', el, function (d) { - return '' + _.escape(d.label) + '
'; - }); - } - - /** - * Renders chart titles - * - * @method render - * @returns {D3.Selection|D3.Transition.Transition} DOM element with chart titles - */ - ChartTitle.prototype.render = function () { - let el = d3.select(this.el).select('.chart-title').node(); - let width = el ? el.clientWidth : 0; - let height = el ? el.clientHeight : 0; + /** + * Renders chart titles + * + * @method render + * @returns {D3.Selection|D3.Transition.Transition} DOM element with chart titles + */ + render() { + const el = d3.select(this.el).select('.chart-title').node(); + const width = el ? el.clientWidth : 0; + const height = el ? el.clientHeight : 0; + + return d3.select(this.el).selectAll('.chart-title').call(this.draw(width, height)); + }; - return d3.select(this.el).selectAll('.chart-title').call(this.draw(width, height)); - }; + /** + * Truncates chart title text + * + * @method truncate + * @param size {Number} Height or width of the HTML Element + * @returns {Function} Truncates text + */ + truncate(size) { + const self = this; + + return function (selection) { + selection.each(function () { + const text = d3.select(this); + const n = text[0].length; + const maxWidth = size / n * 0.9; + const length = this.getComputedTextLength(); + let str; + let avg; + let end; + + if (length > maxWidth) { + str = text.text(); + avg = length / str.length; + end = Math.floor(maxWidth / avg) - 5; + str = str.substr(0, end) + '...'; + self.addMouseEvents(text); + + return text.text(str); + } - /** - * Truncates chart title text - * - * @method truncate - * @param size {Number} Height or width of the HTML Element - * @returns {Function} Truncates text - */ - ChartTitle.prototype.truncate = function (size) { - let self = this; - - return function (selection) { - selection.each(function () { - let text = d3.select(this); - let n = text[0].length; - let maxWidth = size / n * 0.9; - let length = this.getComputedTextLength(); - let str; - let avg; - let end; - - if (length > maxWidth) { - str = text.text(); - avg = length / str.length; - end = Math.floor(maxWidth / avg) - 5; - str = str.substr(0, end) + '...'; - self.addMouseEvents(text); - - return text.text(str); - } - - return text.text(); - }); + return text.text(); + }); + }; }; - }; - /** - * Adds tooltip events on truncated chart titles - * - * @method addMouseEvents - * @param target {HTMLElement} DOM element to attach event listeners - * @returns {*} DOM element with event listeners attached - */ - ChartTitle.prototype.addMouseEvents = function (target) { - if (this.tooltip) { - return target.call(this.tooltip.render()); - } - }; + /** + * Adds tooltip events on truncated chart titles + * + * @method addMouseEvents + * @param target {HTMLElement} DOM element to attach event listeners + * @returns {*} DOM element with event listeners attached + */ + addMouseEvents(target) { + if (this.tooltip) { + return target.call(this.tooltip.render()); + } + }; - /** - * Appends chart titles to the visualization - * - * @method draw - * @returns {Function} Appends chart titles to a D3 selection - */ - ChartTitle.prototype.draw = function (width, height) { - let self = this; - - return function (selection) { - selection.each(function () { - let div = d3.select(this); - let dataType = this.parentNode.__data__.rows ? 'rows' : 'columns'; - let size = dataType === 'rows' ? height : width; - let txtHtOffset = 11; - - self.validateWidthandHeight(width, height); - - div.append('svg') - .attr('width', width) - .attr('height', height) - .append('text') - .attr('transform', function () { - if (dataType === 'rows') { - return 'translate(' + txtHtOffset + ',' + height / 2 + ')rotate(270)'; - } - return 'translate(' + width / 2 + ',' + txtHtOffset + ')'; - }) - .attr('text-anchor', 'middle') - .text(function (d) { return d.label; }); - - // truncate long chart titles - div.selectAll('text') - .call(self.truncate(size)); - }); + /** + * Appends chart titles to the visualization + * + * @method draw + * @returns {Function} Appends chart titles to a D3 selection + */ + draw(width, height) { + const self = this; + + return function (selection) { + selection.each(function () { + const div = d3.select(this); + const dataType = this.parentNode.__data__.rows ? 'rows' : 'columns'; + const size = dataType === 'rows' ? height : width; + const txtHtOffset = 11; + + self.validateWidthandHeight(width, height); + + div.append('svg') + .attr('width', width) + .attr('height', height) + .append('text') + .attr('transform', function () { + if (dataType === 'rows') { + return 'translate(' + txtHtOffset + ',' + height / 2 + ')rotate(270)'; + } + return 'translate(' + width / 2 + ',' + txtHtOffset + ')'; + }) + .attr('text-anchor', 'middle') + .text(function (d) { + return d.label; + }); + + // truncate long chart titles + div.selectAll('text') + .call(self.truncate(size)); + }); + }; }; - }; + } return ChartTitle; }; diff --git a/src/ui/public/vislib/lib/data.js b/src/ui/public/vislib/lib/data.js index 5adceeb12ec45..a04e53daeb9a6 100644 --- a/src/ui/public/vislib/lib/data.js +++ b/src/ui/public/vislib/lib/data.js @@ -1,16 +1,15 @@ import d3 from 'd3'; import _ from 'lodash'; -import errors from 'ui/errors'; import VislibComponentsZeroInjectionInjectZerosProvider from 'ui/vislib/components/zero_injection/inject_zeros'; import VislibComponentsZeroInjectionOrderedXKeysProvider from 'ui/vislib/components/zero_injection/ordered_x_keys'; import VislibComponentsLabelsLabelsProvider from 'ui/vislib/components/labels/labels'; import VislibComponentsColorColorProvider from 'ui/vislib/components/color/color'; export default function DataFactory(Private) { - let injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); - let orderKeys = Private(VislibComponentsZeroInjectionOrderedXKeysProvider); - let getLabels = Private(VislibComponentsLabelsLabelsProvider); - let color = Private(VislibComponentsColorColorProvider); + const injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); + const orderKeys = Private(VislibComponentsZeroInjectionOrderedXKeysProvider); + const getLabels = Private(VislibComponentsLabelsLabelsProvider); + const color = Private(VislibComponentsColorColorProvider); /** * Provides an API for pulling values off the data @@ -21,36 +20,35 @@ export default function DataFactory(Private) { * @param data {Object} Elasticsearch query results * @param attr {Object|*} Visualization options */ - function Data(data, attr, uiState) { - if (!(this instanceof Data)) { - return new Data(data, attr, uiState); - } - - this.uiState = uiState; - - let self = this; - let offset; - - if (attr.mode === 'stacked') { - offset = 'zero'; - } else if (attr.mode === 'percentage') { - offset = 'expand'; - } else if (attr.mode === 'grouped') { - offset = 'group'; - } else { - offset = attr.mode; - } + class Data { + constructor(data, attr, uiState) { + this.uiState = uiState; + + const self = this; + let offset; + + if (attr.mode === 'stacked') { + offset = 'zero'; + } else if (attr.mode === 'percentage') { + offset = 'expand'; + } else if (attr.mode === 'grouped') { + offset = 'group'; + } else { + offset = attr.mode; + } - this.data = data; - this.type = this.getDataType(); + this.data = data; + this.type = this.getDataType(); - this.labels = this._getLabels(this.data); - this.color = this.labels ? color(this.labels, uiState.get('vis.colors')) : undefined; - this._normalizeOrdered(); + this.labels = this._getLabels(this.data); + this.color = this.labels ? color(this.labels, uiState.get('vis.colors')) : undefined; + this._normalizeOrdered(); - this._attr = _.defaults(attr || {}, { - stack: d3.layout.stack() - .x(function (d) { return d.x; }) + this._attr = _.defaults(attr || {}, { + stack: d3.layout.stack() + .x(function (d) { + return d.x; + }) .y(function (d) { if (offset === 'expand') { return Math.abs(d.y); @@ -58,668 +56,666 @@ export default function DataFactory(Private) { return d.y; }) .offset(offset || 'zero') - }); - - if (attr.mode === 'stacked' && attr.type === 'histogram') { - this._attr.stack.out(function (d, y0, y) { - return self._stackNegAndPosVals(d, y0, y); }); - } - } - Data.prototype._updateData = function () { - if (this.data.rows) { - _.map(this.data.rows, this._updateDataSeriesLabel, this); - } else if (this.data.columns) { - _.map(this.data.columns, this._updateDataSeriesLabel, this); - } else { - this._updateDataSeriesLabel(this.data); - } - }; - - Data.prototype._updateDataSeriesLabel = function (eachData) { - if (eachData.series) { - eachData.series[0].label = this.get('yAxisLabel'); - } - }; - - Data.prototype._getLabels = function (data) { - if (this.type === 'series') { - let noLabel = getLabels(data).length === 1 && getLabels(data)[0] === ''; - if (noLabel) { - this._updateData(); - return [(this.get('yAxisLabel'))]; + if (attr.mode === 'stacked' && attr.type === 'histogram') { + this._attr.stack.out(function (d, y0, y) { + return self._stackNegAndPosVals(d, y0, y); + }); } - return getLabels(data); } - return this.pieNames(); - }; - - /** - * Returns true for positive numbers - */ - Data.prototype._isPositive = function (num) { - return num >= 0; - }; - - /** - * Returns true for negative numbers - */ - Data.prototype._isNegative = function (num) { - return num < 0; - }; - /** - * Adds two input values - */ - Data.prototype._addVals = function (a, b) { - return a + b; - }; + _updateData() { + if (this.data.rows) { + _.map(this.data.rows, this._updateDataSeriesLabel, this); + } else if (this.data.columns) { + _.map(this.data.columns, this._updateDataSeriesLabel, this); + } else { + this._updateDataSeriesLabel(this.data); + } + }; - /** - * Returns the results of the addition of numbers in a filtered array. - */ - Data.prototype._sumYs = function (arr, callback) { - let filteredArray = arr.filter(callback); + _updateDataSeriesLabel(eachData) { + if (eachData.series) { + eachData.series[0].label = this.get('yAxisLabel'); + } + }; - return (filteredArray.length) ? filteredArray.reduce(this._addVals) : 0; - }; + _getLabels(data) { + if (this.type === 'series') { + const noLabel = getLabels(data).length === 1 && getLabels(data)[0] === ''; + if (noLabel) { + this._updateData(); + return [(this.get('yAxisLabel'))]; + } + return getLabels(data); + } + return this.pieNames(); + }; - /** - * Calculates the d.y0 value for stacked data in D3. - */ - Data.prototype._calcYZero = function (y, arr) { - if (y >= 0) return this._sumYs(arr, this._isPositive); - return this._sumYs(arr, this._isNegative); - }; + /** + * Returns true for positive numbers + */ + _isPositive(num) { + return num >= 0; + }; - /** - * - */ - Data.prototype._getCounts = function (i, j) { - let data = this.chartData(); - let dataLengths = {}; + /** + * Returns true for negative numbers + */ + _isNegative(num) { + return num < 0; + }; - dataLengths.charts = data.length; - dataLengths.stacks = dataLengths.charts ? data[i].series.length : 0; - dataLengths.values = dataLengths.stacks ? data[i].series[j].values.length : 0; + /** + * Adds two input values + */ + _addVals(a, b) { + return a + b; + }; - return dataLengths; - }; + /** + * Returns the results of the addition of numbers in a filtered array. + */ + _sumYs(arr, callback) { + const filteredArray = arr.filter(callback); - /** - * - */ - Data.prototype._createCache = function () { - let cache = { - index: { - chart: 0, - stack: 0, - value: 0 - }, - yValsArr: [] + return (filteredArray.length) ? filteredArray.reduce(this._addVals) : 0; }; - cache.count = this._getCounts(cache.index.chart, cache.index.stack); + /** + * Calculates the d.y0 value for stacked data in D3. + */ + _calcYZero(y, arr) { + if (y >= 0) return this._sumYs(arr, this._isPositive); + return this._sumYs(arr, this._isNegative); + }; - return cache; - }; + /** + * + */ + _getCounts(i, j) { + const data = this.chartData(); + const dataLengths = {}; - /** - * Stacking function passed to the D3 Stack Layout `.out` API. - * See: https://github.com/mbostock/d3/wiki/Stack-Layout - * It is responsible for calculating the correct d.y0 value for - * mixed datasets containing both positive and negative values. - */ - Data.prototype._stackNegAndPosVals = function (d, y0, y) { - let data = this.chartData(); - - // Storing counters and data characteristics needed to stack values properly - if (!this._cache) { - this._cache = this._createCache(); - } + dataLengths.charts = data.length; + dataLengths.stacks = dataLengths.charts ? data[i].series.length : 0; + dataLengths.values = dataLengths.stacks ? data[i].series[j].values.length : 0; - d.y0 = this._calcYZero(y, this._cache.yValsArr); - ++this._cache.index.stack; + return dataLengths; + }; + /** + * + */ + _createCache() { + const cache = { + index: { + chart: 0, + stack: 0, + value: 0 + }, + yValsArr: [] + }; - // last stack, or last value, reset the stack count and y value array - let lastStack = (this._cache.index.stack >= this._cache.count.stacks); - if (lastStack) { - this._cache.index.stack = 0; - ++this._cache.index.value; - this._cache.yValsArr = []; - // still building the stack collection, push v value to array - } else if (y !== 0) { - this._cache.yValsArr.push(y); - } + cache.count = this._getCounts(cache.index.chart, cache.index.stack); - // last value, prepare for the next chart, if one exists - let lastValue = (this._cache.index.value >= this._cache.count.values); - if (lastValue) { - this._cache.index.value = 0; - ++this._cache.index.chart; + return cache; + }; - // no more charts, reset the queue and finish - if (this._cache.index.chart >= this._cache.count.charts) { + /** + * Stacking function passed to the D3 Stack Layout `.out` API. + * See: https://github.com/mbostock/d3/wiki/Stack-Layout + * It is responsible for calculating the correct d.y0 value for + * mixed datasets containing both positive and negative values. + */ + _stackNegAndPosVals(d, y0, y) { + const data = this.chartData(); + + // Storing counters and data characteristics needed to stack values properly + if (!this._cache) { this._cache = this._createCache(); - return; } - // get stack and value count for next chart - let chartSeries = data[this._cache.index.chart].series; - this._cache.count.stacks = chartSeries.length; - this._cache.count.values = chartSeries.length ? chartSeries[this._cache.index.stack].values.length : 0; - } - }; - - Data.prototype.getDataType = function () { - let data = this.getVisData(); - let type; - - data.forEach(function (obj) { - if (obj.series) { - type = 'series'; - } else if (obj.slices) { - type = 'slices'; - } else if (obj.geoJson) { - type = 'geoJson'; + d.y0 = this._calcYZero(y, this._cache.yValsArr); + ++this._cache.index.stack; + + + // last stack, or last value, reset the stack count and y value array + const lastStack = (this._cache.index.stack >= this._cache.count.stacks); + if (lastStack) { + this._cache.index.stack = 0; + ++this._cache.index.value; + this._cache.yValsArr = []; + // still building the stack collection, push v value to array + } else if (y !== 0) { + this._cache.yValsArr.push(y); } - }); - return type; - }; + // last value, prepare for the next chart, if one exists + const lastValue = (this._cache.index.value >= this._cache.count.values); + if (lastValue) { + this._cache.index.value = 0; + ++this._cache.index.chart; + + // no more charts, reset the queue and finish + if (this._cache.index.chart >= this._cache.count.charts) { + this._cache = this._createCache(); + return; + } + + // get stack and value count for next chart + const chartSeries = data[this._cache.index.chart].series; + this._cache.count.stacks = chartSeries.length; + this._cache.count.values = chartSeries.length ? chartSeries[this._cache.index.stack].values.length : 0; + } + }; - /** - * Returns an array of the actual x and y data value objects - * from data with series keys - * - * @method chartData - * @returns {*} Array of data objects - */ - Data.prototype.chartData = function () { - if (!this.data.series) { - let arr = this.data.rows ? this.data.rows : this.data.columns; - return _.toArray(arr); - } - return [this.data]; - }; + getDataType() { + const data = this.getVisData(); + let type; + + data.forEach(function (obj) { + if (obj.series) { + type = 'series'; + } else if (obj.slices) { + type = 'slices'; + } else if (obj.geoJson) { + type = 'geoJson'; + } + }); - /** - * Returns an array of chart data objects - * - * @method getVisData - * @returns {*} Array of chart data objects - */ - Data.prototype.getVisData = function () { - let visData; - - if (this.data.rows) { - visData = this.data.rows; - } else if (this.data.columns) { - visData = this.data.columns; - } else { - visData = [this.data]; - } + return type; + }; - return visData; - }; + /** + * Returns an array of the actual x and y data value objects + * from data with series keys + * + * @method chartData + * @returns {*} Array of data objects + */ + chartData() { + if (!this.data.series) { + const arr = this.data.rows ? this.data.rows : this.data.columns; + return _.toArray(arr); + } + return [this.data]; + }; - /** - * get min and max for all cols, rows of data - * - * @method getMaxMin - * @return {Object} - */ - Data.prototype.getGeoExtents = function () { - let visData = this.getVisData(); + /** + * Returns an array of chart data objects + * + * @method getVisData + * @returns {*} Array of chart data objects + */ + getVisData() { + let visData; + + if (this.data.rows) { + visData = this.data.rows; + } else if (this.data.columns) { + visData = this.data.columns; + } else { + visData = [this.data]; + } - return _.reduce(_.pluck(visData, 'geoJson.properties'), function (minMax, props) { - return { - min: Math.min(props.min, minMax.min), - max: Math.max(props.max, minMax.max) - }; - }, { min: Infinity, max: -Infinity }); - }; + return visData; + }; - /** - * Returns array of chart data objects for pie data objects - * - * @method pieData - * @returns {*} Array of chart data objects - */ - Data.prototype.pieData = function () { - if (!this.data.slices) { - return this.data.rows ? this.data.rows : this.data.columns; - } - return [this.data]; - }; + /** + * get min and max for all cols, rows of data + * + * @method getMaxMin + * @return {Object} + */ + getGeoExtents() { + const visData = this.getVisData(); + + return _.reduce(_.pluck(visData, 'geoJson.properties'), function (minMax, props) { + return { + min: Math.min(props.min, minMax.min), + max: Math.max(props.max, minMax.max) + }; + }, {min: Infinity, max: -Infinity}); + }; - /** - * Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter` - * pulls the value off the first item in the array - * these values are typically the same between data objects of the same chart - * TODO: May need to verify this or refactor - * - * @method get - * @param thing {String} Data object key - * @returns {*} Data object value - */ - Data.prototype.get = function (thing, def) { - let source = (this.data.rows || this.data.columns || [this.data])[0]; - return _.get(source, thing, def); - }; + /** + * Returns array of chart data objects for pie data objects + * + * @method pieData + * @returns {*} Array of chart data objects + */ + pieData() { + if (!this.data.slices) { + return this.data.rows ? this.data.rows : this.data.columns; + } + return [this.data]; + }; - /** - * Returns true if null values are present - * @returns {*} - */ - Data.prototype.hasNullValues = function () { - let chartData = this.chartData(); + /** + * Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter` + * pulls the value off the first item in the array + * these values are typically the same between data objects of the same chart + * TODO: May need to verify this or refactor + * + * @method get + * @param thing {String} Data object key + * @returns {*} Data object value + */ + get(thing, def) { + const source = (this.data.rows || this.data.columns || [this.data])[0]; + return _.get(source, thing, def); + }; - return chartData.some(function (chart) { - return chart.series.some(function (obj) { - return obj.values.some(function (d) { - return d.y === null; + /** + * Returns true if null values are present + * @returns {*} + */ + hasNullValues() { + const chartData = this.chartData(); + + return chartData.some(function (chart) { + return chart.series.some(function (obj) { + return obj.values.some(function (d) { + return d.y === null; + }); }); }); - }); - }; - - /** - * Return an array of all value objects - * Pluck the data.series array from each data object - * Create an array of all the value objects from the series array - * - * @method flatten - * @returns {Array} Value objects - */ - Data.prototype.flatten = function () { - return _(this.chartData()) - .pluck('series') - .flattenDeep() - .pluck('values') - .flattenDeep() - .value(); - }; - - /** - * Determines whether histogram charts should be stacked - * TODO: need to make this more generic - * - * @method shouldBeStacked - * @returns {boolean} - */ - Data.prototype.shouldBeStacked = function () { - let isHistogram = (this._attr.type === 'histogram'); - let isArea = (this._attr.type === 'area'); - let isOverlapping = (this._attr.mode === 'overlap'); - let grouped = (this._attr.mode === 'grouped'); - - let stackedHisto = isHistogram && !grouped; - let stackedArea = isArea && !isOverlapping; - - return stackedHisto || stackedArea; - }; - - /** - * Validates that the Y axis min value defined by user input - * is a number. - * - * @param val {Number} Y axis min value - * @returns {Number} Y axis min value - */ - Data.prototype.validateUserDefinedYMin = function (val) { - if (!_.isNumber(val)) { - throw new Error('validateUserDefinedYMin expects a number'); - } - return val; - }; - - /** - * Calculates the lowest Y value across all charts, taking - * stacking into consideration. - * - * @method getYMin - * @param {function} [getValue] - optional getter that will receive a - * point and should return the value that should - * be considered - * @returns {Number} Min y axis value - */ - Data.prototype.getYMin = function (getValue) { - let self = this; - let arr = []; + }; - if (this._attr.mode === 'percentage' || this._attr.mode === 'wiggle' || - this._attr.mode === 'silhouette') { - return 0; - } + /** + * Return an array of all value objects + * Pluck the data.series array from each data object + * Create an array of all the value objects from the series array + * + * @method flatten + * @returns {Array} Value objects + */ + flatten() { + return _(this.chartData()) + .pluck('series') + .flattenDeep() + .pluck('values') + .flattenDeep() + .value(); + }; - let flat = this.flatten(); - // if there is only one data point and its less than zero, - // return 0 as the yMax value. - if (!flat.length || flat.length === 1 && flat[0].y > 0) { - return 0; - } + /** + * Determines whether histogram charts should be stacked + * TODO: need to make this more generic + * + * @method shouldBeStacked + * @returns {boolean} + */ + shouldBeStacked() { + const isHistogram = (this._attr.type === 'histogram'); + const isArea = (this._attr.type === 'area'); + const isOverlapping = (this._attr.mode === 'overlap'); + const grouped = (this._attr.mode === 'grouped'); + + const stackedHisto = isHistogram && !grouped; + const stackedArea = isArea && !isOverlapping; + + return stackedHisto || stackedArea; + }; - let min = Infinity; + /** + * Validates that the Y axis min value defined by user input + * is a number. + * + * @param val {Number} Y axis min value + * @returns {Number} Y axis min value + */ + validateUserDefinedYMin(val) { + if (!_.isNumber(val)) { + throw new Error('validateUserDefinedYMin expects a number'); + } + return val; + }; - // for each object in the dataArray, - // push the calculated y value to the initialized array (arr) - _.each(this.chartData(), function (chart) { - let calculatedMin = self._getYExtent(chart, 'min', getValue); - if (!_.isUndefined(calculatedMin)) { - min = Math.min(min, calculatedMin); + /** + * Calculates the lowest Y value across all charts, taking + * stacking into consideration. + * + * @method getYMin + * @param {function} [getValue] - optional getter that will receive a + * point and should return the value that should + * be considered + * @returns {Number} Min y axis value + */ + getYMin(getValue) { + const self = this; + + if (this._attr.mode === 'percentage' || this._attr.mode === 'wiggle' || + this._attr.mode === 'silhouette') { + return 0; } - }); - return min; - }; + const flat = this.flatten(); + // if there is only one data point and its less than zero, + // return 0 as the yMax value. + if (!flat.length || flat.length === 1 && flat[0].y > 0) { + return 0; + } - /** - * Calculates the highest Y value across all charts, taking - * stacking into consideration. - * - * @method getYMax - * @param {function} [getValue] - optional getter that will receive a - * point and should return the value that should - * be considered - * @returns {Number} Max y axis value - */ - Data.prototype.getYMax = function (getValue) { - let self = this; - let arr = []; + let min = Infinity; - if (self._attr.mode === 'percentage') { - return 1; - } + // for each object in the dataArray, + // push the calculated y value to the initialized array (arr) + _.each(this.chartData(), function (chart) { + const calculatedMin = self._getYExtent(chart, 'min', getValue); + if (!_.isUndefined(calculatedMin)) { + min = Math.min(min, calculatedMin); + } + }); - let flat = this.flatten(); - // if there is only one data point and its less than zero, - // return 0 as the yMax value. - if (!flat.length || flat.length === 1 && flat[0].y < 0) { - return 0; - } + return min; + }; - let max = -Infinity; + /** + * Calculates the highest Y value across all charts, taking + * stacking into consideration. + * + * @method getYMax + * @param {function} [getValue] - optional getter that will receive a + * point and should return the value that should + * be considered + * @returns {Number} Max y axis value + */ + getYMax(getValue) { + const self = this; + + if (self._attr.mode === 'percentage') { + return 1; + } - // for each object in the dataArray, - // push the calculated y value to the initialized array (arr) - _.each(this.chartData(), function (chart) { - let calculatedMax = self._getYExtent(chart, 'max', getValue); - if (!_.isUndefined(calculatedMax)) { - max = Math.max(max, calculatedMax); + const flat = this.flatten(); + // if there is only one data point and its less than zero, + // return 0 as the yMax value. + if (!flat.length || flat.length === 1 && flat[0].y < 0) { + return 0; } - }); - return max; - }; + let max = -Infinity; - /** - * Calculates the stacked values for each data object - * - * @method stackData - * @param series {Array} Array of data objects - * @returns {*} Array of data objects with x, y, y0 keys - */ - Data.prototype.stackData = function (series) { - // Should not stack values on line chart - if (this._attr.type === 'line') return series; - return this._attr.stack(series); - }; + // for each object in the dataArray, + // push the calculated y value to the initialized array (arr) + _.each(this.chartData(), function (chart) { + const calculatedMax = self._getYExtent(chart, 'max', getValue); + if (!_.isUndefined(calculatedMax)) { + max = Math.max(max, calculatedMax); + } + }); - /** - * Returns the max Y axis value for a `series` array based on - * a specified callback function (calculation). - * @param {function} [getValue] - Optional getter that will be used to read - * values from points when calculating the extent. - * default is either this._getYStack or this.getY - * based on this.shouldBeStacked(). - */ - Data.prototype._getYExtent = function (chart, extent, getValue) { - if (this.shouldBeStacked()) { - this.stackData(_.pluck(chart.series, 'values')); - getValue = getValue || this._getYStack; - } else { - getValue = getValue || this._getY; - } + return max; + }; - let points = chart.series - .reduce(function (points, series) { - return points.concat(series.values); - }, []) - .map(getValue); + /** + * Calculates the stacked values for each data object + * + * @method stackData + * @param series {Array} Array of data objects + * @returns {*} Array of data objects with x, y, y0 keys + */ + stackData(series) { + // Should not stack values on line chart + if (this._attr.type === 'line') return series; + return this._attr.stack(series); + }; - return d3[extent](points); - }; + /** + * Returns the max Y axis value for a `series` array based on + * a specified callback function (calculation). + * @param {function} [getValue] - Optional getter that will be used to read + * values from points when calculating the extent. + * default is either this._getYStack or this.getY + * based on this.shouldBeStacked(). + */ + _getYExtent(chart, extent, getValue) { + if (this.shouldBeStacked()) { + this.stackData(_.pluck(chart.series, 'values')); + getValue = getValue || this._getYStack; + } else { + getValue = getValue || this._getY; + } - /** - * Calculates the y stack value for each data object - */ - Data.prototype._getYStack = function (d) { - return d.y0 + d.y; - }; + const points = chart.series + .reduce(function (points, series) { + return points.concat(series.values); + }, []) + .map(getValue); - /** - * Calculates the Y max value - */ - Data.prototype._getY = function (d) { - return d.y; - }; + return d3[extent](points); + }; - /** - * Helper function for getNames - * Returns an array of objects with a name (key) value and an index value. - * The index value allows us to sort the names in the correct nested order. - * - * @method returnNames - * @param array {Array} Array of data objects - * @param index {Number} Number of times the object is nested - * @param columns {Object} Contains name formatter information - * @returns {Array} Array of labels (strings) - */ - Data.prototype.returnNames = function (array, index, columns) { - let names = []; - let self = this; - - _.forEach(array, function (obj, i) { - names.push({ - label: obj.name, - values: obj, - index: index - }); + /** + * Calculates the y stack value for each data object + */ + _getYStack(d) { + return d.y0 + d.y; + }; - if (obj.children) { - let plusIndex = index + 1; + /** + * Calculates the Y max value + */ + _getY(d) { + return d.y; + }; - _.forEach(self.returnNames(obj.children, plusIndex, columns), function (namedObj) { - names.push(namedObj); + /** + * Helper function for getNames + * Returns an array of objects with a name (key) value and an index value. + * The index value allows us to sort the names in the correct nested order. + * + * @method returnNames + * @param array {Array} Array of data objects + * @param index {Number} Number of times the object is nested + * @param columns {Object} Contains name formatter information + * @returns {Array} Array of labels (strings) + */ + returnNames(array, index, columns) { + const names = []; + const self = this; + + _.forEach(array, function (obj, i) { + names.push({ + label: obj.name, + values: obj, + index: index }); - } - }); - return names; - }; + if (obj.children) { + const plusIndex = index + 1; - /** - * Flattens hierarchical data into an array of objects with a name and index value. - * The indexed value determines the order of nesting in the data. - * Returns an array with names sorted by the index value. - * - * @method getNames - * @param data {Object} Chart data object - * @param columns {Object} Contains formatter information - * @returns {Array} Array of names (strings) - */ - Data.prototype.getNames = function (data, columns) { - let slices = data.slices; - - if (slices.children) { - let namedObj = this.returnNames(slices.children, 0, columns); - - return _(namedObj) - .sortBy(function (obj) { - return obj.index; - }) - .unique(function (d) { - return d.label; - }) - .value(); - } - }; - - /** - * Removes zeros from pie chart data - * @param slices - * @returns {*} - */ - Data.prototype._removeZeroSlices = function (slices) { - let self = this; + _.forEach(self.returnNames(obj.children, plusIndex, columns), function (namedObj) { + names.push(namedObj); + }); + } + }); - if (!slices.children) return slices; + return names; + }; - slices = _.clone(slices); - slices.children = slices.children.reduce(function (children, child) { - if (child.size !== 0) { - children.push(self._removeZeroSlices(child)); + /** + * Flattens hierarchical data into an array of objects with a name and index value. + * The indexed value determines the order of nesting in the data. + * Returns an array with names sorted by the index value. + * + * @method getNames + * @param data {Object} Chart data object + * @param columns {Object} Contains formatter information + * @returns {Array} Array of names (strings) + */ + getNames(data, columns) { + const slices = data.slices; + + if (slices.children) { + const namedObj = this.returnNames(slices.children, 0, columns); + + return _(namedObj) + .sortBy(function (obj) { + return obj.index; + }) + .unique(function (d) { + return d.label; + }) + .value(); } - return children; - }, []); - - return slices; - }; - - /** - * Returns an array of names ordered by appearance in the nested array - * of objects - * - * @method pieNames - * @returns {Array} Array of unique names (strings) - */ - Data.prototype.pieNames = function (data) { - let self = this; - let names = []; + }; - _.forEach(data, function (obj) { - let columns = obj.raw ? obj.raw.columns : undefined; - obj.slices = self._removeZeroSlices(obj.slices); + /** + * Removes zeros from pie chart data + * @param slices + * @returns {*} + */ + _removeZeroSlices(slices) { + const self = this; + + if (!slices.children) return slices; + + slices = _.clone(slices); + slices.children = slices.children.reduce(function (children, child) { + if (child.size !== 0) { + children.push(self._removeZeroSlices(child)); + } + return children; + }, []); + + return slices; + }; - _.forEach(self.getNames(obj, columns), function (name) { - names.push(name); + /** + * Returns an array of names ordered by appearance in the nested array + * of objects + * + * @method pieNames + * @returns {Array} Array of unique names (strings) + */ + pieNames(data) { + const self = this; + const names = []; + + _.forEach(data, function (obj) { + const columns = obj.raw ? obj.raw.columns : undefined; + obj.slices = self._removeZeroSlices(obj.slices); + + _.forEach(self.getNames(obj, columns), function (name) { + names.push(name); + }); }); - }); - - return _.uniq(names, 'label'); - }; - - /** - * Inject zeros into the data - * - * @method injectZeros - * @returns {Object} Data object with zeros injected - */ - Data.prototype.injectZeros = function () { - return injectZeros(this.data); - }; - /** - * Returns an array of all x axis values from the data - * - * @method xValues - * @returns {Array} Array of x axis values - */ - Data.prototype.xValues = function () { - return orderKeys(this.data); - }; - - /** - * Return an array of unique labels - * Curently, only used for vertical bar and line charts, - * or any data object with series values - * - * @method getLabels - * @returns {Array} Array of labels (strings) - */ - Data.prototype.getLabels = function () { - return getLabels(this.data); - }; + return _.uniq(names, 'label'); + }; - /** - * Returns a function that does color lookup on labels - * - * @method getColorFunc - * @returns {Function} Performs lookup on string and returns hex color - */ - Data.prototype.getColorFunc = function () { - return color(this.getLabels(), this.uiState.get('vis.colors')); - }; + /** + * Inject zeros into the data + * + * @method injectZeros + * @returns {Object} Data object with zeros injected + */ + injectZeros() { + return injectZeros(this.data); + }; - /** - * Returns a function that does color lookup on names for pie charts - * - * @method getPieColorFunc - * @returns {Function} Performs lookup on string and returns hex color - */ - Data.prototype.getPieColorFunc = function () { - return color(this.pieNames(this.getVisData()).map(function (d) { - return d.label; - }), this.uiState.get('vis.colors')); - }; + /** + * Returns an array of all x axis values from the data + * + * @method xValues + * @returns {Array} Array of x axis values + */ + xValues() { + return orderKeys(this.data); + }; - /** - * ensure that the datas ordered property has a min and max - * if the data represents an ordered date range. - * - * @return {undefined} - */ - Data.prototype._normalizeOrdered = function () { - let data = this.getVisData(); - let self = this; + /** + * Return an array of unique labels + * Curently, only used for vertical bar and line charts, + * or any data object with series values + * + * @method getLabels + * @returns {Array} Array of labels (strings) + */ + getLabels() { + return getLabels(this.data); + }; - data.forEach(function (d) { - if (!d.ordered || !d.ordered.date) return; + /** + * Returns a function that does color lookup on labels + * + * @method getColorFunc + * @returns {Function} Performs lookup on string and returns hex color + */ + getColorFunc() { + return color(this.getLabels(), this.uiState.get('vis.colors')); + }; - let missingMin = d.ordered.min == null; - let missingMax = d.ordered.max == null; + /** + * Returns a function that does color lookup on names for pie charts + * + * @method getPieColorFunc + * @returns {Function} Performs lookup on string and returns hex color + */ + getPieColorFunc() { + return color(this.pieNames(this.getVisData()).map(function (d) { + return d.label; + }), this.uiState.get('vis.colors')); + }; - if (missingMax || missingMin) { - let extent = d3.extent(self.xValues()); - if (missingMin) d.ordered.min = extent[0]; - if (missingMax) d.ordered.max = extent[1]; - } - }); - }; + /** + * ensure that the datas ordered property has a min and max + * if the data represents an ordered date range. + * + * @return {undefined} + */ + _normalizeOrdered() { + const data = this.getVisData(); + const self = this; + + data.forEach(function (d) { + if (!d.ordered || !d.ordered.date) return; + + const missingMin = d.ordered.min == null; + const missingMax = d.ordered.max == null; + + if (missingMax || missingMin) { + const extent = d3.extent(self.xValues()); + if (missingMin) d.ordered.min = extent[0]; + if (missingMax) d.ordered.max = extent[1]; + } + }); + }; - /** - * Calculates min and max values for all map data - * series.rows is an array of arrays - * each row is an array of values - * last value in row array is bucket count - * - * @method mapDataExtents - * @param series {Array} Array of data objects - * @returns {Array} min and max values - */ - Data.prototype.mapDataExtents = function (series) { - let values; - values = _.map(series.rows, function (row) { - return row[row.length - 1]; - }); - let extents = [_.min(values), _.max(values)]; - return extents; - }; + /** + * Calculates min and max values for all map data + * series.rows is an array of arrays + * each row is an array of values + * last value in row array is bucket count + * + * @method mapDataExtents + * @param series {Array} Array of data objects + * @returns {Array} min and max values + */ + mapDataExtents(series) { + let values; + values = _.map(series.rows, function (row) { + return row[row.length - 1]; + }); + return [_.min(values), _.max(values)]; + }; - /** - * Get the maximum number of series, considering each chart - * individually. - * - * @return {number} - the largest number of series from all charts - */ - Data.prototype.maxNumberOfSeries = function () { - return this.chartData().reduce(function (max, chart) { - return Math.max(max, chart.series.length); - }, 0); - }; + /** + * Get the maximum number of series, considering each chart + * individually. + * + * @return {number} - the largest number of series from all charts + */ + maxNumberOfSeries() { + return this.chartData().reduce(function (max, chart) { + return Math.max(max, chart.series.length); + }, 0); + }; + } return Data; }; diff --git a/src/ui/public/vislib/lib/dispatch.js b/src/ui/public/vislib/lib/dispatch.js index fd2820f1a4028..98f03a3ffdd35 100644 --- a/src/ui/public/vislib/lib/dispatch.js +++ b/src/ui/public/vislib/lib/dispatch.js @@ -4,8 +4,6 @@ import $ from 'jquery'; import SimpleEmitter from 'ui/utils/simple_emitter'; import VislibComponentsTooltipProvider from 'ui/vislib/components/tooltip'; export default function DispatchClass(Private) { - let Tooltip = Private(VislibComponentsTooltipProvider); - /** * Handles event responses * @@ -14,300 +12,298 @@ export default function DispatchClass(Private) { * @param handler {Object} Reference to Handler Class Object */ - _.class(Dispatch).inherits(SimpleEmitter); - function Dispatch(handler) { - if (!(this instanceof Dispatch)) { - return new Dispatch(handler); + class Dispatch extends SimpleEmitter { + constructor(handler) { + super(); + this.handler = handler; + this._listeners = {}; } - Dispatch.Super.call(this); - this.handler = handler; - this._listeners = {}; - } - - /** - * Response to click and hover events - * - * @param d {Object} Data point - * @param i {Number} Index number of data point - * @returns {{value: *, point: *, label: *, color: *, pointIndex: *, + /** + * Response to click and hover events + * + * @param d {Object} Data point + * @param i {Number} Index number of data point + * @returns {{value: *, point: *, label: *, color: *, pointIndex: *, * series: *, config: *, data: (Object|*), * e: (d3.event|*), handler: (Object|*)}} Event response object - */ - Dispatch.prototype.eventResponse = function (d, i) { - let datum = d._input || d; - let data = d3.event.target.nearestViewportElement ? - d3.event.target.nearestViewportElement.__data__ : d3.event.target.__data__; - let label = d.label ? d.label : d.name; - let isSeries = !!(data && data.series); - let isSlices = !!(data && data.slices); - let series = isSeries ? data.series : undefined; - let slices = isSlices ? data.slices : undefined; - let handler = this.handler; - let color = _.get(handler, 'data.color'); - let isPercentage = (handler && handler._attr.mode === 'percentage'); - - let eventData = { - value: d.y, - point: datum, - datum: datum, - label: label, - color: color ? color(label) : undefined, - pointIndex: i, - series: series, - slices: slices, - config: handler && handler._attr, - data: data, - e: d3.event, - handler: handler - }; + */ + eventResponse(d, i) { + const datum = d._input || d; + const data = d3.event.target.nearestViewportElement ? + d3.event.target.nearestViewportElement.__data__ : d3.event.target.__data__; + const label = d.label ? d.label : d.name; + const isSeries = !!(data && data.series); + const isSlices = !!(data && data.slices); + const series = isSeries ? data.series : undefined; + const slices = isSlices ? data.slices : undefined; + const handler = this.handler; + const color = _.get(handler, 'data.color'); + const isPercentage = (handler && handler._attr.mode === 'percentage'); + + const eventData = { + value: d.y, + point: datum, + datum: datum, + label: label, + color: color ? color(label) : undefined, + pointIndex: i, + series: series, + slices: slices, + config: handler && handler._attr, + data: data, + e: d3.event, + handler: handler + }; - if (isSeries) { - // Find object with the actual d value and add it to the point object - let object = _.find(series, { 'label': d.label }); - eventData.value = +object.values[i].y; + if (isSeries) { + // Find object with the actual d value and add it to the point object + const object = _.find(series, {'label': d.label}); + eventData.value = +object.values[i].y; - if (isPercentage) { - // Add the formatted percentage to the point object - eventData.percent = (100 * d.y).toFixed(1) + '%'; + if (isPercentage) { + // Add the formatted percentage to the point object + eventData.percent = (100 * d.y).toFixed(1) + '%'; + } } - } - - return eventData; - }; - /** - * Returns a function that adds events and listeners to a D3 selection - * - * @method addEvent - * @param event {String} - * @param callback {Function} - * @returns {Function} - */ - Dispatch.prototype.addEvent = function (event, callback) { - return function (selection) { - selection.each(function () { - let element = d3.select(this); - - if (typeof callback === 'function') { - return element.on(event, callback); - } - }); + return eventData; }; - }; - /** - * - * @method addHoverEvent - * @returns {Function} - */ - Dispatch.prototype.addHoverEvent = function () { - let self = this; - let isClickable = this.listenerCount('click') > 0; - let addEvent = this.addEvent; - let $el = this.handler.el; - if (!this.handler.highlight) { - this.handler.highlight = self.highlight; - } + /** + * Returns a function that adds events and listeners to a D3 selection + * + * @method addEvent + * @param event {String} + * @param callback {Function} + * @returns {Function} + */ + addEvent(event, callback) { + return function (selection) { + selection.each(function () { + const element = d3.select(this); + + if (typeof callback === 'function') { + return element.on(event, callback); + } + }); + }; + }; - function hover(d, i) { - // Add pointer if item is clickable - if (isClickable) { - self.addMousePointer.call(this, arguments); + /** + * + * @method addHoverEvent + * @returns {Function} + */ + addHoverEvent() { + const self = this; + const isClickable = this.listenerCount('click') > 0; + const addEvent = this.addEvent; + const $el = this.handler.el; + if (!this.handler.highlight) { + this.handler.highlight = self.highlight; } - self.handler.highlight.call(this, $el); - self.emit('hover', self.eventResponse(d, i)); - } - - return addEvent('mouseover', hover); - }; - - /** - * - * @method addMouseoutEvent - * @returns {Function} - */ - Dispatch.prototype.addMouseoutEvent = function () { - let self = this; - let addEvent = this.addEvent; - let $el = this.handler.el; - if (!this.handler.unHighlight) { - this.handler.unHighlight = self.unHighlight; - } - - function mouseout() { - self.handler.unHighlight.call(this, $el); - } - - return addEvent('mouseout', mouseout); - }; - - /** - * - * @method addClickEvent - * @returns {Function} - */ - Dispatch.prototype.addClickEvent = function () { - let self = this; - let addEvent = this.addEvent; - - function click(d, i) { - self.emit('click', self.eventResponse(d, i)); - } + function hover(d, i) { + // Add pointer if item is clickable + if (isClickable) { + self.addMousePointer.call(this, arguments); + } - return addEvent('click', click); - }; + self.handler.highlight.call(this, $el); + self.emit('hover', self.eventResponse(d, i)); + } - /** - * Determine if we will allow brushing - * - * @method allowBrushing - * @returns {Boolean} - */ - Dispatch.prototype.allowBrushing = function () { - let xAxis = this.handler.xAxis; - // Don't allow brushing for time based charts from non-time-based indices - let hasTimeField = this.handler.vis._attr.hasTimeField; + return addEvent('mouseover', hover); + }; - return Boolean(hasTimeField && xAxis.ordered && xAxis.xScale && _.isFunction(xAxis.xScale.invert)); - }; + /** + * + * @method addMouseoutEvent + * @returns {Function} + */ + addMouseoutEvent() { + const self = this; + const addEvent = this.addEvent; + const $el = this.handler.el; + if (!this.handler.unHighlight) { + this.handler.unHighlight = self.unHighlight; + } - /** - * Determine if brushing is currently enabled - * - * @method isBrushable - * @returns {Boolean} - */ - Dispatch.prototype.isBrushable = function () { - return this.allowBrushing() && this.listenerCount('brush') > 0; - }; + function mouseout() { + self.handler.unHighlight.call(this, $el); + } - /** - * - * @param svg - * @returns {Function} - */ - Dispatch.prototype.addBrushEvent = function (svg) { - if (!this.isBrushable()) return; + return addEvent('mouseout', mouseout); + }; - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.xAxis.yScale; - let brush = this.createBrush(xScale, svg); + /** + * + * @method addClickEvent + * @returns {Function} + */ + addClickEvent() { + const self = this; + const addEvent = this.addEvent; + + function click(d, i) { + self.emit('click', self.eventResponse(d, i)); + } - function brushEnd() { - if (!validBrushClick(d3.event)) return; + return addEvent('click', click); + }; - let bar = d3.select(this); - let startX = d3.mouse(svg.node()); - let startXInv = xScale.invert(startX[0]); + /** + * Determine if we will allow brushing + * + * @method allowBrushing + * @returns {Boolean} + */ + allowBrushing() { + const xAxis = this.handler.xAxis; + // Don't allow brushing for time based charts from non-time-based indices + const hasTimeField = this.handler.vis._attr.hasTimeField; + + return Boolean(hasTimeField && xAxis.ordered && xAxis.xScale && _.isFunction(xAxis.xScale.invert)); + }; - // Reset the brush value - brush.extent([startXInv, startXInv]); + /** + * Determine if brushing is currently enabled + * + * @method isBrushable + * @returns {Boolean} + */ + isBrushable() { + return this.allowBrushing() && this.listenerCount('brush') > 0; + }; - // Magic! - // Need to call brush on svg to see brush when brushing - // while on top of bars. - // Need to call brush on bar to allow the click event to be registered - svg.call(brush); - bar.call(brush); - } + /** + * + * @param svg + * @returns {Function} + */ + addBrushEvent(svg) { + if (!this.isBrushable()) return; + + const xScale = this.handler.xAxis.xScale; + const brush = this.createBrush(xScale, svg); + + function brushEnd() { + if (!validBrushClick(d3.event)) return; + + const bar = d3.select(this); + const startX = d3.mouse(svg.node()); + const startXInv = xScale.invert(startX[0]); + + // Reset the brush value + brush.extent([startXInv, startXInv]); + + // Magic! + // Need to call brush on svg to see brush when brushing + // while on top of bars. + // Need to call brush on bar to allow the click event to be registered + svg.call(brush); + bar.call(brush); + } - return this.addEvent('mousedown', brushEnd); - }; + return this.addEvent('mousedown', brushEnd); + }; - /** - * Mouseover Behavior - * - * @method addMousePointer - * @returns {D3.Selection} - */ - Dispatch.prototype.addMousePointer = function () { - return d3.select(this).style('cursor', 'pointer'); - }; + /** + * Mouseover Behavior + * + * @method addMousePointer + * @returns {D3.Selection} + */ + addMousePointer() { + return d3.select(this).style('cursor', 'pointer'); + }; - /** - * Mouseover Behavior - * - * @param element {D3.Selection} - * @method highlight - */ - Dispatch.prototype.highlight = function (element) { - let label = this.getAttribute('data-label'); - if (!label) return; - //Opacity 1 is needed to avoid the css application - $('[data-label]', element.parentNode).css('opacity', 1).not( - function (els, el) { return `${$(el).data('label')}` === label;} - ).css('opacity', 0.5); - }; + /** + * Mouseover Behavior + * + * @param element {D3.Selection} + * @method highlight + */ + highlight(element) { + const label = this.getAttribute('data-label'); + if (!label) return; + //Opacity 1 is needed to avoid the css application + $('[data-label]', element.parentNode).css('opacity', 1).not( + function (els, el) { + return `${$(el).data('label')}` === label; + } + ).css('opacity', 0.5); + }; - /** - * Mouseout Behavior - * - * @param element {D3.Selection} - * @method unHighlight - */ - Dispatch.prototype.unHighlight = function (element) { - $('[data-label]', element.parentNode).css('opacity', 1); - }; + /** + * Mouseout Behavior + * + * @param element {D3.Selection} + * @method unHighlight + */ + unHighlight(element) { + $('[data-label]', element.parentNode).css('opacity', 1); + }; - /** - * Adds D3 brush to SVG and returns the brush function - * - * @param xScale {Function} D3 xScale function - * @param svg {HTMLElement} Reference to SVG - * @returns {*} Returns a D3 brush function and a SVG with a brush group attached - */ - Dispatch.prototype.createBrush = function (xScale, svg) { - let self = this; - let attr = self.handler._attr; - let height = attr.height; - let margin = attr.margin; - - // Brush scale - let brush = d3.svg.brush() - .x(xScale) - .on('brushend', function brushEnd() { - - // Assumes data is selected at the chart level - // In this case, the number of data objects should always be 1 - let data = d3.select(this).data()[0]; - let isTimeSeries = (data.ordered && data.ordered.date); - - // Allows for brushing on d3.scale.ordinal() - let selected = xScale.domain().filter(function (d) { - return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]); - }); - let range = isTimeSeries ? brush.extent() : selected; + /** + * Adds D3 brush to SVG and returns the brush function + * + * @param xScale {Function} D3 xScale function + * @param svg {HTMLElement} Reference to SVG + * @returns {*} Returns a D3 brush function and a SVG with a brush group attached + */ + createBrush(xScale, svg) { + const self = this; + const attr = self.handler._attr; + const height = attr.height; + const margin = attr.margin; + + // Brush scale + const brush = d3.svg.brush() + .x(xScale) + .on('brushend', function brushEnd() { + + // Assumes data is selected at the chart level + // In this case, the number of data objects should always be 1 + const data = d3.select(this).data()[0]; + const isTimeSeries = (data.ordered && data.ordered.date); + + // Allows for brushing on d3.scale.ordinal() + const selected = xScale.domain().filter(function (d) { + return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]); + }); + const range = isTimeSeries ? brush.extent() : selected; - return self.emit('brush', { - range: range, - config: attr, - e: d3.event, - data: data - }); - }); - - // if `addBrushing` is true, add brush canvas - if (self.listenerCount('brush')) { - svg.insert('g', 'g') - .attr('class', 'brush') - .call(brush) - .call(function (brushG) { - // hijack the brush start event to filter out right/middle clicks - let brushHandler = brushG.on('mousedown.brush'); - if (!brushHandler) return; // touch events in use - brushG.on('mousedown.brush', function () { - if (validBrushClick(d3.event)) brushHandler.apply(this, arguments); + return self.emit('brush', { + range: range, + config: attr, + e: d3.event, + data: data }); - }) - .selectAll('rect') - .attr('height', height - margin.top - margin.bottom); + }); - return brush; - } - }; + // if `addBrushing` is true, add brush canvas + if (self.listenerCount('brush')) { + svg.insert('g', 'g') + .attr('class', 'brush') + .call(brush) + .call(function (brushG) { + // hijack the brush start event to filter out right/middle clicks + const brushHandler = brushG.on('mousedown.brush'); + if (!brushHandler) return; // touch events in use + brushG.on('mousedown.brush', function () { + if (validBrushClick(d3.event)) brushHandler.apply(this, arguments); + }); + }) + .selectAll('rect') + .attr('height', height - margin.top - margin.bottom); + + return brush; + } + }; + } function validBrushClick(event) { return event.button === 0; diff --git a/src/ui/public/vislib/lib/handler/handler.js b/src/ui/public/vislib/lib/handler/handler.js index a74c627931860..18aa28ab5686b 100644 --- a/src/ui/public/vislib/lib/handler/handler.js +++ b/src/ui/public/vislib/lib/handler/handler.js @@ -6,8 +6,8 @@ import VislibLibDataProvider from 'ui/vislib/lib/data'; import VislibLibLayoutLayoutProvider from 'ui/vislib/lib/layout/layout'; export default function HandlerBaseClass(Private) { - let Data = Private(VislibLibDataProvider); - let Layout = Private(VislibLibLayoutLayoutProvider); + const Data = Private(VislibLibDataProvider); + const Layout = Private(VislibLibLayoutLayoutProvider); /** * Handles building all the components of the visualization @@ -18,195 +18,192 @@ export default function HandlerBaseClass(Private) { * @param opts {Object} Reference to Visualization constructors needed to * create the visualization */ - function Handler(vis, opts) { - if (!(this instanceof Handler)) { - return new Handler(vis, opts); - } - - this.data = opts.data || new Data(vis.data, vis._attr, vis.uiState); - this.vis = vis; - this.el = vis.el; - this.ChartClass = vis.ChartClass; - this.charts = []; - - this._attr = _.defaults(vis._attr || {}, { - 'margin' : { top: 10, right: 3, bottom: 5, left: 3 } - }); - - this.xAxis = opts.xAxis; - this.yAxis = opts.yAxis; - this.chartTitle = opts.chartTitle; - this.axisTitle = opts.axisTitle; - this.alerts = opts.alerts; - - this.layout = new Layout(vis.el, vis.data, vis._attr.type, opts); - this.binder = new Binder(); - this.renderArray = _.filter([ - this.layout, - this.axisTitle, - this.chartTitle, - this.alerts, - this.xAxis, - this.yAxis, - ], Boolean); - - // memoize so that the same function is returned every time, - // allowing us to remove/re-add the same function - this.getProxyHandler = _.memoize(function (event) { - let self = this; - return function (e) { - self.vis.emit(event, e); - }; - }); - } + class Handler { + constructor(vis, opts) { - /** - * Validates whether data is actually present in the data object - * used to render the Vis. Throws a no results error if data is not - * present. - * - * @private - */ - Handler.prototype._validateData = function () { - let dataType = this.data.type; + this.data = opts.data || new Data(vis.data, vis._attr, vis.uiState); + this.vis = vis; + this.el = vis.el; + this.ChartClass = vis.ChartClass; + this.charts = []; - if (!dataType) { - throw new errors.NoResults(); - } - }; + this._attr = _.defaults(vis._attr || {}, { + 'margin': {top: 10, right: 3, bottom: 5, left: 3} + }); - /** - * Renders the constructors that create the visualization, - * including the chart constructor - * - * @method render - * @returns {HTMLElement} With the visualization child element - */ - Handler.prototype.render = function () { - let self = this; - let charts = this.charts = []; - let selection = d3.select(this.el); + this.xAxis = opts.xAxis; + this.yAxis = opts.yAxis; + this.chartTitle = opts.chartTitle; + this.axisTitle = opts.axisTitle; + this.alerts = opts.alerts; + + this.layout = new Layout(vis.el, vis.data, vis._attr.type, opts); + this.binder = new Binder(); + this.renderArray = _.filter([ + this.layout, + this.axisTitle, + this.chartTitle, + this.alerts, + this.xAxis, + this.yAxis, + ], Boolean); + + // memoize so that the same function is returned every time, + // allowing us to remove/re-add the same function + this.getProxyHandler = _.memoize(function (event) { + const self = this; + return function (e) { + self.vis.emit(event, e); + }; + }); - selection.selectAll('*').remove(); + /** + * Enables events, i.e. binds specific events to the chart + * object(s) `on` method. For example, `click` or `mousedown` events. + * + * @method enable + * @param event {String} Event type + * @param chart {Object} Chart + * @returns {*} + */ + this.enable = this.chartEventProxyToggle('on'); + + /** + * Disables events for all charts + * + * @method disable + * @param event {String} Event type + * @param chart {Object} Chart + * @returns {*} + */ + this.disable = this.chartEventProxyToggle('off'); + } - this._validateData(); - this.renderArray.forEach(function (property) { - if (typeof property.render === 'function') { - property.render(); + /** + * Validates whether data is actually present in the data object + * used to render the Vis. Throws a no results error if data is not + * present. + * + * @private + */ + _validateData() { + const dataType = this.data.type; + + if (!dataType) { + throw new errors.NoResults(); } - }); - - // render the chart(s) - selection.selectAll('.chart') - .each(function (chartData) { - let chart = new self.ChartClass(self, this, chartData); + }; - self.vis.activeEvents().forEach(function (event) { - self.enable(event, chart); + /** + * Renders the constructors that create the visualization, + * including the chart constructor + * + * @method render + * @returns {HTMLElement} With the visualization child element + */ + render() { + const self = this; + const charts = this.charts = []; + const selection = d3.select(this.el); + + selection.selectAll('*').remove(); + + this._validateData(); + this.renderArray.forEach(function (property) { + if (typeof property.render === 'function') { + property.render(); + } }); - charts.push(chart); - chart.render(); - }); - }; - - - /** - * Enables events, i.e. binds specific events to the chart - * object(s) `on` method. For example, `click` or `mousedown` events. - * - * @method enable - * @param event {String} Event type - * @param chart {Object} Chart - * @returns {*} - */ - Handler.prototype.enable = chartEventProxyToggle('on'); + // render the chart(s) + selection.selectAll('.chart') + .each(function (chartData) { + const chart = new self.ChartClass(self, this, chartData); - /** - * Disables events for all charts - * - * @method disable - * @param event {String} Event type - * @param chart {Object} Chart - * @returns {*} - */ - Handler.prototype.disable = chartEventProxyToggle('off'); + self.vis.activeEvents().forEach(function (event) { + self.enable(event, chart); + }); - - function chartEventProxyToggle(method) { - return function (event, chart) { - let proxyHandler = this.getProxyHandler(event); - - _.each(chart ? [chart] : this.charts, function (chart) { - chart.events[method](event, proxyHandler); + charts.push(chart); + chart.render(); }); }; - } - /** - * Removes all DOM elements from the HTML element provided - * - * @method removeAll - * @param el {HTMLElement} Reference to the HTML Element that - * contains the chart - * @returns {D3.Selection|D3.Transition.Transition} With the chart - * child element removed - */ - Handler.prototype.removeAll = function (el) { - return d3.select(el).selectAll('*').remove(); - }; + chartEventProxyToggle(method) { + return function (event, chart) { + const proxyHandler = this.getProxyHandler(event); - /** - * Displays an error message in the DOM - * - * @method error - * @param message {String} Error message to display - * @returns {HTMLElement} Displays the input message - */ - Handler.prototype.error = function (message) { - this.removeAll(this.el); - - let div = d3.select(this.el) - .append('div') - // class name needs `chart` in it for the polling checkSize function - // to continuously call render on resize - .attr('class', 'visualize-error chart error'); - - if (message === 'No results found') { - div.append('div') - .attr('class', 'text-center visualize-error visualize-chart ng-scope') - .append('div').attr('class', 'item top') - .append('div').attr('class', 'item') - .append('h2').html('') - .append('h4').text(message); - - div.append('div').attr('class', 'item bottom'); - return div; + _.each(chart ? [chart] : this.charts, function (chart) { + chart.events[method](event, proxyHandler); + }); + }; } - return div.append('h4').text(message); - }; - - /** - * Destroys all the charts in the visualization - * - * @method destroy - */ - Handler.prototype.destroy = function () { - this.binder.destroy(); + /** + * Removes all DOM elements from the HTML element provided + * + * @method removeAll + * @param el {HTMLElement} Reference to the HTML Element that + * contains the chart + * @returns {D3.Selection|D3.Transition.Transition} With the chart + * child element removed + */ + removeAll(el) { + return d3.select(el).selectAll('*').remove(); + }; - this.renderArray.forEach(function (renderable) { - if (_.isFunction(renderable.destroy)) { - renderable.destroy(); + /** + * Displays an error message in the DOM + * + * @method error + * @param message {String} Error message to display + * @returns {HTMLElement} Displays the input message + */ + error(message) { + this.removeAll(this.el); + + const div = d3.select(this.el) + .append('div') + // class name needs `chart` in it for the polling checkSize function + // to continuously call render on resize + .attr('class', 'visualize-error chart error'); + + if (message === 'No results found') { + div.append('div') + .attr('class', 'text-center visualize-error visualize-chart ng-scope') + .append('div').attr('class', 'item top') + .append('div').attr('class', 'item') + .append('h2').html('') + .append('h4').text(message); + + div.append('div').attr('class', 'item bottom'); + return div; } - }); - this.charts.splice(0).forEach(function (chart) { - if (_.isFunction(chart.destroy)) { - chart.destroy(); - } - }); - }; + return div.append('h4').text(message); + }; + + /** + * Destroys all the charts in the visualization + * + * @method destroy + */ + destroy() { + this.binder.destroy(); + + this.renderArray.forEach(function (renderable) { + if (_.isFunction(renderable.destroy)) { + renderable.destroy(); + } + }); + + this.charts.splice(0).forEach(function (chart) { + if (_.isFunction(chart.destroy)) { + chart.destroy(); + } + }); + }; + } return Handler; }; diff --git a/src/ui/public/vislib/lib/handler/handler_types.js b/src/ui/public/vislib/lib/handler/handler_types.js index 3034bef76e5cc..5473181a97f68 100644 --- a/src/ui/public/vislib/lib/handler/handler_types.js +++ b/src/ui/public/vislib/lib/handler/handler_types.js @@ -3,7 +3,7 @@ import VislibLibHandlerTypesPieProvider from 'ui/vislib/lib/handler/types/pie'; import VislibLibHandlerTypesTileMapProvider from 'ui/vislib/lib/handler/types/tile_map'; export default function HandlerTypeFactory(Private) { - let pointSeries = Private(VislibLibHandlerTypesPointSeriesProvider); + const pointSeries = Private(VislibLibHandlerTypesPointSeriesProvider); /** * Handles the building of each visualization diff --git a/src/ui/public/vislib/lib/handler/types/pie.js b/src/ui/public/vislib/lib/handler/types/pie.js index c5ff5042855a2..f119f21f60042 100644 --- a/src/ui/public/vislib/lib/handler/types/pie.js +++ b/src/ui/public/vislib/lib/handler/types/pie.js @@ -1,11 +1,9 @@ import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; -import VislibLibDataProvider from 'ui/vislib/lib/data'; import VislibLibChartTitleProvider from 'ui/vislib/lib/chart_title'; export default function PieHandler(Private) { - let Handler = Private(VislibLibHandlerHandlerProvider); - let Data = Private(VislibLibDataProvider); - let ChartTitle = Private(VislibLibChartTitleProvider); + const Handler = Private(VislibLibHandlerHandlerProvider); + const ChartTitle = Private(VislibLibChartTitleProvider); /* * Handler for Pie visualizations. 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 3e1f1d3aa070b..10bd2d651aaeb 100644 --- a/src/ui/public/vislib/lib/handler/types/point_series.js +++ b/src/ui/public/vislib/lib/handler/types/point_series.js @@ -8,15 +8,22 @@ import VislibLibChartTitleProvider from 'ui/vislib/lib/chart_title'; import VislibLibAlertsProvider from 'ui/vislib/lib/alerts'; export default function ColumnHandler(Private) { - let injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); - let Handler = Private(VislibLibHandlerHandlerProvider); - let Data = Private(VislibLibDataProvider); - let XAxis = Private(VislibLibXAxisProvider); - let YAxis = Private(VislibLibYAxisProvider); - let AxisTitle = Private(VislibLibAxisTitleProvider); - let ChartTitle = Private(VislibLibChartTitleProvider); - let Alerts = Private(VislibLibAlertsProvider); + const injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); + const Handler = Private(VislibLibHandlerHandlerProvider); + const Data = Private(VislibLibDataProvider); + const XAxis = Private(VislibLibXAxisProvider); + const YAxis = Private(VislibLibYAxisProvider); + const AxisTitle = Private(VislibLibAxisTitleProvider); + const ChartTitle = Private(VislibLibChartTitleProvider); + const Alerts = Private(VislibLibAlertsProvider); + function getData(vis, opts) { + if (opts.zeroFill) { + return new Data(injectZeros(vis.data), vis._attr, vis.uiState); + } else { + return new Data(vis.data, vis._attr, vis.uiState); + } + } /* * Create handlers for Area, Column, and Line charts which * are all nearly the same minus a few details @@ -25,14 +32,8 @@ export default function ColumnHandler(Private) { opts = opts || {}; return function (vis) { - let isUserDefinedYAxis = vis._attr.setYExtents; - let data; - - if (opts.zeroFill) { - data = new Data(injectZeros(vis.data), vis._attr, vis.uiState); - } else { - data = new Data(vis.data, vis._attr, vis.uiState); - } + const isUserDefinedYAxis = vis._attr.setYExtents; + const data = getData(vis, opts); return new Handler(vis, { data: data, @@ -78,8 +79,8 @@ export default function ColumnHandler(Private) { test: function (vis, data) { if (!data.shouldBeStacked() || data.maxNumberOfSeries() < 2) return; - let hasPos = data.getYMax(data._getY) > 0; - let hasNeg = data.getYMin(data._getY) < 0; + const hasPos = data.getYMax(data._getY) > 0; + const hasNeg = data.getYMin(data._getY) < 0; return (hasPos && hasNeg); } }, 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 60609c5349d9b..c6ac20996a4fe 100644 --- a/src/ui/public/vislib/lib/handler/types/tile_map.js +++ b/src/ui/public/vislib/lib/handler/types/tile_map.js @@ -1,15 +1,14 @@ -import _ from 'lodash'; import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; import VislibLibDataProvider from 'ui/vislib/lib/data'; export default function MapHandlerProvider(Private) { - let Handler = Private(VislibLibHandlerHandlerProvider); - let Data = Private(VislibLibDataProvider); + const Handler = Private(VislibLibHandlerHandlerProvider); + const Data = Private(VislibLibDataProvider); return function (vis) { - let data = new Data(vis.data, vis._attr, vis.uiState); + const data = new Data(vis.data, vis._attr, vis.uiState); - let MapHandler = new Handler(vis, { + const MapHandler = new Handler(vis, { data: data }); diff --git a/src/ui/public/vislib/lib/layout/layout.js b/src/ui/public/vislib/lib/layout/layout.js index e0141dc71ce53..3e71225a00d92 100644 --- a/src/ui/public/vislib/lib/layout/layout.js +++ b/src/ui/public/vislib/lib/layout/layout.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import VislibLibLayoutLayoutTypesProvider from 'ui/vislib/lib/layout/layout_types'; export default function LayoutFactory(Private) { - let layoutType = Private(VislibLibLayoutLayoutTypesProvider); + const layoutType = Private(VislibLibLayoutLayoutTypesProvider); /** * Builds the visualization DOM layout @@ -21,133 +21,131 @@ export default function LayoutFactory(Private) { * @param data {Object} Elasticsearch query results for this specific chart * @param chartType {Object} Reference to chart functions, i.e. Pie */ - function Layout(el, data, chartType, opts) { - if (!(this instanceof Layout)) { - return new Layout(el, data, chartType, opts); + class Layout { + constructor(el, data, chartType, opts) { + this.el = el; + this.data = data; + this.opts = opts; + this.layoutType = layoutType[chartType](this.el, this.data); } - this.el = el; - this.data = data; - this.opts = opts; - this.layoutType = layoutType[chartType](this.el, this.data); - } - - // Render the layout - /** - * Renders visualization HTML layout - * Remove all elements from the current visualization and creates the layout - * - * @method render - */ - Layout.prototype.render = function () { - this.removeAll(this.el); - this.createLayout(this.layoutType); - }; - - /** - * Create the layout based on the json array provided - * for each object in the layout array, call the layout function - * - * @method createLayout - * @param arr {Array} Json array - * @returns {*} Creates the visualization layout - */ - Layout.prototype.createLayout = function (arr) { - let self = this; - - return _.each(arr, function (obj) { - self.layout(obj); - }); - }; - - /** - * Appends a DOM element based on the object keys - * check to see if reference to DOM element is string but not class selector - * Create a class selector - * - * @method layout - * @param obj {Object} Instructions for creating the layout of a DOM Element - * @returns {*} DOM Element - */ - Layout.prototype.layout = function (obj) { - if (!obj.parent) { - throw new Error('No parent element provided'); - } - - if (!obj.type) { - throw new Error('No element type provided'); - } - - if (typeof obj.type !== 'string') { - throw new Error(obj.type + ' must be a string'); - } - - if (typeof obj.parent === 'string' && obj.parent.charAt(0) !== '.') { - obj.parent = '.' + obj.parent; - } - - let childEl = this.appendElem(obj.parent, obj.type, obj.class); - - if (obj.datum) { - childEl.datum(obj.datum); - } - - if (obj.splits) { - childEl.call(obj.splits, obj.parent, this.opts); - } - - if (obj.children) { - let newParent = childEl[0][0]; - - _.forEach(obj.children, function (obj) { - if (!obj.parent) { - obj.parent = newParent; - } + // Render the layout + /** + * Renders visualization HTML layout + * Remove all elements from the current visualization and creates the layout + * + * @method render + */ + render() { + this.removeAll(this.el); + this.createLayout(this.layoutType); + }; + + /** + * Create the layout based on the json array provided + * for each object in the layout array, call the layout function + * + * @method createLayout + * @param arr {Array} Json array + * @returns {*} Creates the visualization layout + */ + createLayout(arr) { + const self = this; + + return _.each(arr, function (obj) { + self.layout(obj); }); - - this.createLayout(obj.children); - } - - return childEl; - }; - - /** - * Appends a `type` of DOM element to `el` and gives it a class name attribute `className` - * - * @method appendElem - * @param el {HTMLElement} Reference to a DOM Element - * @param type {String} DOM element type - * @param className {String} CSS class name - * @returns {*} Reference to D3 Selection - */ - Layout.prototype.appendElem = function (el, type, className) { - if (!el || !type || !className) { - throw new Error('Function requires that an el, type, and class be provided'); - } - - if (typeof el === 'string') { - // Create a DOM reference with a d3 selection - // Need to make sure that the `el` is bound to this object - // to prevent it from being appended to another Layout - el = d3.select(this.el) - .select(el)[0][0]; - } - - return d3.select(el) + }; + + /** + * Appends a DOM element based on the object keys + * check to see if reference to DOM element is string but not class selector + * Create a class selector + * + * @method layout + * @param obj {Object} Instructions for creating the layout of a DOM Element + * @returns {*} DOM Element + */ + layout(obj) { + if (!obj.parent) { + throw new Error('No parent element provided'); + } + + if (!obj.type) { + throw new Error('No element type provided'); + } + + if (typeof obj.type !== 'string') { + throw new Error(obj.type + ' must be a string'); + } + + if (typeof obj.parent === 'string' && obj.parent.charAt(0) !== '.') { + obj.parent = '.' + obj.parent; + } + + const childEl = this.appendElem(obj.parent, obj.type, obj.class); + + if (obj.datum) { + childEl.datum(obj.datum); + } + + if (obj.splits) { + childEl.call(obj.splits, obj.parent, this.opts); + } + + if (obj.children) { + const newParent = childEl[0][0]; + + _.forEach(obj.children, function (obj) { + if (!obj.parent) { + obj.parent = newParent; + } + }); + + this.createLayout(obj.children); + } + + return childEl; + }; + + /** + * Appends a `type` of DOM element to `el` and gives it a class name attribute `className` + * + * @method appendElem + * @param el {HTMLElement} Reference to a DOM Element + * @param type {String} DOM element type + * @param className {String} CSS class name + * @returns {*} Reference to D3 Selection + */ + appendElem(el, type, className) { + if (!el || !type || !className) { + throw new Error('Function requires that an el, type, and class be provided'); + } + + if (typeof el === 'string') { + // Create a DOM reference with a d3 selection + // Need to make sure that the `el` is bound to this object + // to prevent it from being appended to another Layout + el = d3.select(this.el) + .select(el)[0][0]; + } + + return d3.select(el) .append(type) .attr('class', className); - }; - - /** - * Removes all DOM elements from DOM element - * - * @method removeAll - * @param el {HTMLElement} Reference to DOM element - * @returns {D3.Selection|D3.Transition.Transition} Reference to an empty DOM element - */ - Layout.prototype.removeAll = function (el) { - return d3.select(el).selectAll('*').remove(); - }; + }; + + /** + * Removes all DOM elements from DOM element + * + * @method removeAll + * @param el {HTMLElement} Reference to DOM element + * @returns {D3.Selection|D3.Transition.Transition} Reference to an empty DOM element + */ + removeAll(el) { + return d3.select(el).selectAll('*').remove(); + }; + } return Layout; }; diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js index 80f0b09651b6c..f32fc7a3ce364 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js @@ -9,7 +9,7 @@ define(function () { */ return function split(selection) { selection.each(function (data) { - let div = d3.select(this) + const div = d3.select(this) .attr('class', function () { if (data.rows) { return 'chart-wrapper-row'; @@ -21,7 +21,7 @@ define(function () { }); let divClass; - let charts = div.selectAll('charts') + const charts = div.selectAll('charts') .append('div') .data(function (d) { if (d.rows) { diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js index a6f1860b5b31c..eaeb1947ef8ef 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js @@ -13,11 +13,11 @@ define(function () { */ return function (selection) { selection.each(function (data) { - let div = d3.select(this); - let parent = $(this).parents('.vis-wrapper'); + const div = d3.select(this); + const parent = $(this).parents('.vis-wrapper'); if (!data.series) { - let splits = div.selectAll('.chart-title') + div.selectAll('.chart-title') .data(function (d) { return d.rows ? d.rows : d.columns; }) diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js index a827e50ad672e..2d99d4bbd25bc 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js @@ -10,7 +10,7 @@ define(function () { return function (selection) { selection.each(function () { - let div = d3.select(this); + const div = d3.select(this); div.selectAll('.x-axis-div') .append('div') diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js index 797e681075aab..93d8188073a18 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js @@ -10,10 +10,10 @@ define(function () { // render and get bounding box width return function (selection, parent, opts) { - let yAxis = opts && opts.yAxis; + const yAxis = opts && opts.yAxis; selection.each(function () { - let div = d3.select(this); + const div = d3.select(this); div.call(setWidth, yAxis); @@ -31,14 +31,14 @@ define(function () { function setWidth(el, yAxis) { if (!yAxis) return; - let padding = 5; - let height = parseInt(el.node().clientHeight, 10); + const padding = 5; + const height = parseInt(el.node().clientHeight, 10); // render svg and get the width of the bounding box - let svg = d3.select('body') + const svg = d3.select('body') .append('svg') .attr('style', 'position:absolute; top:-10000; left:-10000'); - let width = svg.append('g') + const width = svg.append('g') .call(yAxis.getYAxis(height)).node().getBBox().width + padding; svg.remove(); diff --git a/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js b/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js index 445a4ffb1e369..af58177503590 100644 --- a/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js +++ b/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js @@ -10,7 +10,7 @@ define(function () { return function split(selection) { selection.each(function (data) { - let div = d3.select(this) + const div = d3.select(this) .attr('class', function () { if (data.rows) { return 'chart-wrapper-row'; @@ -22,7 +22,7 @@ define(function () { }); let divClass; - let charts = div.selectAll('charts') + const charts = div.selectAll('charts') .append('div') .data(function (d) { if (d.rows) { diff --git a/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js b/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js index dd07c5e70180e..d9c70f5ddff44 100644 --- a/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js +++ b/src/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js @@ -12,7 +12,7 @@ define(function () { return function (selection, parent) { selection.each(function (data) { - let div = d3.select(this); + const div = d3.select(this); if (!data.slices) { div.selectAll('.chart-title') diff --git a/src/ui/public/vislib/lib/layout/splits/tile_map/map_split.js b/src/ui/public/vislib/lib/layout/splits/tile_map/map_split.js index 7e8116968b246..710c77083927a 100644 --- a/src/ui/public/vislib/lib/layout/splits/tile_map/map_split.js +++ b/src/ui/public/vislib/lib/layout/splits/tile_map/map_split.js @@ -9,7 +9,7 @@ define(function () { */ return function split(selection) { selection.each(function (data) { - let div = d3.select(this) + const div = d3.select(this) .attr('class', function () { // Determine the parent class if (data.rows) { @@ -22,7 +22,7 @@ define(function () { }); let divClass; - let charts = div.selectAll('charts') + const charts = div.selectAll('charts') .append('div') .data(function (d) { // Determine the child class 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 04062740841f2..b4bd968d2ae9c 100644 --- a/src/ui/public/vislib/lib/layout/types/column_layout.js +++ b/src/ui/public/vislib/lib/layout/types/column_layout.js @@ -1,14 +1,13 @@ -import d3 from 'd3'; import VislibLibLayoutSplitsColumnChartChartSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/chart_split'; import VislibLibLayoutSplitsColumnChartYAxisSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/y_axis_split'; import VislibLibLayoutSplitsColumnChartXAxisSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/x_axis_split'; import VislibLibLayoutSplitsColumnChartChartTitleSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/chart_title_split'; export default function ColumnLayoutFactory(Private) { - let chartSplit = Private(VislibLibLayoutSplitsColumnChartChartSplitProvider); - let yAxisSplit = Private(VislibLibLayoutSplitsColumnChartYAxisSplitProvider); - let xAxisSplit = Private(VislibLibLayoutSplitsColumnChartXAxisSplitProvider); - let chartTitleSplit = Private(VislibLibLayoutSplitsColumnChartChartTitleSplitProvider); + const chartSplit = Private(VislibLibLayoutSplitsColumnChartChartSplitProvider); + const yAxisSplit = Private(VislibLibLayoutSplitsColumnChartYAxisSplitProvider); + const xAxisSplit = Private(VislibLibLayoutSplitsColumnChartXAxisSplitProvider); + const chartTitleSplit = Private(VislibLibLayoutSplitsColumnChartChartTitleSplitProvider); /** * Specifies the visualization layout for column charts. diff --git a/src/ui/public/vislib/lib/layout/types/map_layout.js b/src/ui/public/vislib/lib/layout/types/map_layout.js index c6f9e1e3bbc00..64106cdfe1350 100644 --- a/src/ui/public/vislib/lib/layout/types/map_layout.js +++ b/src/ui/public/vislib/lib/layout/types/map_layout.js @@ -1,7 +1,6 @@ -import d3 from 'd3'; import VislibLibLayoutSplitsTileMapMapSplitProvider from 'ui/vislib/lib/layout/splits/tile_map/map_split'; export default function ColumnLayoutFactory(Private) { - let mapSplit = Private(VislibLibLayoutSplitsTileMapMapSplitProvider); + const mapSplit = Private(VislibLibLayoutSplitsTileMapMapSplitProvider); /* * Specifies the visualization layout for tile maps. diff --git a/src/ui/public/vislib/lib/layout/types/pie_layout.js b/src/ui/public/vislib/lib/layout/types/pie_layout.js index 9c7e5132ce683..ea7bedf61405e 100644 --- a/src/ui/public/vislib/lib/layout/types/pie_layout.js +++ b/src/ui/public/vislib/lib/layout/types/pie_layout.js @@ -1,9 +1,8 @@ -import d3 from 'd3'; import VislibLibLayoutSplitsPieChartChartSplitProvider from 'ui/vislib/lib/layout/splits/pie_chart/chart_split'; import VislibLibLayoutSplitsPieChartChartTitleSplitProvider from 'ui/vislib/lib/layout/splits/pie_chart/chart_title_split'; export default function ColumnLayoutFactory(Private) { - let chartSplit = Private(VislibLibLayoutSplitsPieChartChartSplitProvider); - let chartTitleSplit = Private(VislibLibLayoutSplitsPieChartChartTitleSplitProvider); + const chartSplit = Private(VislibLibLayoutSplitsPieChartChartSplitProvider); + const chartTitleSplit = Private(VislibLibLayoutSplitsPieChartChartTitleSplitProvider); /** * Specifies the visualization layout for column charts. diff --git a/src/ui/public/vislib/lib/resize_checker.js b/src/ui/public/vislib/lib/resize_checker.js index 4462630f69a0d..9a86ddc1642ea 100644 --- a/src/ui/public/vislib/lib/resize_checker.js +++ b/src/ui/public/vislib/lib/resize_checker.js @@ -3,12 +3,12 @@ import _ from 'lodash'; import sequencer from 'ui/utils/sequencer'; import EventsProvider from 'ui/events'; import ReflowWatcherProvider from 'ui/reflow_watcher'; -export default function ResizeCheckerFactory(Private, Notifier, $rootScope) { +export default function ResizeCheckerFactory(Private, Notifier) { - let EventEmitter = Private(EventsProvider); - let reflowWatcher = Private(ReflowWatcherProvider); + const EventEmitter = Private(EventsProvider); + const reflowWatcher = Private(ReflowWatcherProvider); - let SCHEDULE = ResizeChecker.SCHEDULE = sequencer.createEaseIn( + const SCHEDULE = ResizeChecker.SCHEDULE = sequencer.createEaseIn( 100, // shortest delay 10000, // longest delay 50 // tick count @@ -16,7 +16,7 @@ export default function ResizeCheckerFactory(Private, Notifier, $rootScope) { // maximum ms that we can delay emitting 'resize'. This is only used // to debounce resizes when the size of the element is constantly changing - let MS_MAX_RESIZE_DELAY = ResizeChecker.MS_MAX_RESIZE_DELAY = 500; + const MS_MAX_RESIZE_DELAY = ResizeChecker.MS_MAX_RESIZE_DELAY = 500; /** * Checks the size of an element on a regular basis. Provides @@ -88,7 +88,7 @@ export default function ResizeCheckerFactory(Private, Notifier, $rootScope) { * @return {boolean} - true if the passed in value matches the saved size */ ResizeChecker.prototype._equalsSavedSize = function (a) { - let b = this._savedSize || {}; + const b = this._savedSize || {}; return a.w === b.w && a.h === b.h; }; @@ -129,12 +129,12 @@ export default function ResizeCheckerFactory(Private, Notifier, $rootScope) { * @return {void} */ ResizeChecker.prototype.check = function () { - let newSize = this.read(); - let dirty = this.saveSize(newSize); - let dirtyChanged = this.saveDirty(dirty); + const newSize = this.read(); + const dirty = this.saveSize(newSize); + const dirtyChanged = this.saveDirty(dirty); - let doneDirty = !dirty && dirtyChanged; - let muchDirty = dirty && (this.lastDirtyChange() - Date.now() > MS_MAX_RESIZE_DELAY); + const doneDirty = !dirty && dirtyChanged; + const muchDirty = dirty && (this.lastDirtyChange() - Date.now() > MS_MAX_RESIZE_DELAY); if (doneDirty || muchDirty) { this.emit('resize', newSize); } @@ -175,10 +175,8 @@ export default function ResizeCheckerFactory(Private, Notifier, $rootScope) { this._tick += 1; } - let check = this.check; // already bound - let tick = this._tick; - let notify = this.notify; - let ms = this._currentSchedule[this._tick]; + const check = this.check; // already bound + const ms = this._currentSchedule[this._tick]; return (this._timerId = setTimeout(function () { check(); }, ms)); diff --git a/src/ui/public/vislib/lib/x_axis.js b/src/ui/public/vislib/lib/x_axis.js index 5e71a38e2e541..b7bdcbc0dc5bd 100644 --- a/src/ui/public/vislib/lib/x_axis.js +++ b/src/ui/public/vislib/lib/x_axis.js @@ -5,7 +5,7 @@ import moment from 'moment'; import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; export default function XAxisFactory(Private) { - let ErrorHandler = Private(VislibLibErrorHandlerProvider); + const ErrorHandler = Private(VislibLibErrorHandlerProvider); /** * Adds an x axis to the visualization @@ -15,512 +15,499 @@ export default function XAxisFactory(Private) { * @param args {{el: (HTMLElement), xValues: (Array), ordered: (Object|*), * xAxisFormatter: (Function), _attr: (Object|*)}} */ - _.class(XAxis).inherits(ErrorHandler); - function XAxis(args) { - if (!(this instanceof XAxis)) { - return new XAxis(args); + class XAxis extends ErrorHandler { + constructor(args) { + super(); + this.el = args.el; + this.xValues = args.xValues; + this.ordered = args.ordered; + this.xAxisFormatter = args.xAxisFormatter; + this.expandLastBucket = args.expandLastBucket == null ? true : args.expandLastBucket; + this._attr = _.defaults(args._attr || {}); } - this.el = args.el; - this.xValues = args.xValues; - this.ordered = args.ordered; - this.xAxisFormatter = args.xAxisFormatter; - this.expandLastBucket = args.expandLastBucket == null ? true : args.expandLastBucket; - this._attr = _.defaults(args._attr || {}); - } - - /** - * Renders the x axis - * - * @method render - * @returns {D3.UpdateSelection} Appends x axis to visualization - */ - XAxis.prototype.render = function () { - d3.select(this.el).selectAll('.x-axis-div').call(this.draw()); - }; - - /** - * Returns d3 x axis scale function. - * If time, return time scale, else return d3 ordinal scale for nominal data - * - * @method getScale - * @returns {*} D3 scale function - */ - XAxis.prototype.getScale = function () { - let ordered = this.ordered; - - if (ordered && ordered.date) { - return d3.time.scale.utc(); - } - return d3.scale.ordinal(); - }; - - /** - * Add domain to the x axis scale. - * if time, return a time domain, and calculate the min date, max date, and time interval - * else, return a nominal (d3.scale.ordinal) domain, i.e. array of x axis values - * - * @method getDomain - * @param scale {Function} D3 scale - * @returns {*} D3 scale function - */ - XAxis.prototype.getDomain = function (scale) { - let ordered = this.ordered; - - if (ordered && ordered.date) { - return this.getTimeDomain(scale, this.xValues); - } - return this.getOrdinalDomain(scale, this.xValues); - }; - - /** - * Returns D3 time domain - * - * @method getTimeDomain - * @param scale {Function} D3 scale function - * @param data {Array} - * @returns {*} D3 scale function - */ - XAxis.prototype.getTimeDomain = function (scale, data) { - return scale.domain([this.minExtent(data), this.maxExtent(data)]); - }; + /** + * Renders the x axis + * + * @method render + * @returns {D3.UpdateSelection} Appends x axis to visualization + */ + render() { + d3.select(this.el).selectAll('.x-axis-div').call(this.draw()); + }; - XAxis.prototype.minExtent = function (data) { - return this._calculateExtent(data || this.xValues, 'min'); - }; + /** + * Returns d3 x axis scale function. + * If time, return time scale, else return d3 ordinal scale for nominal data + * + * @method getScale + * @returns {*} D3 scale function + */ + getScale() { + const ordered = this.ordered; + + if (ordered && ordered.date) { + return d3.time.scale.utc(); + } + return d3.scale.ordinal(); + }; - XAxis.prototype.maxExtent = function (data) { - return this._calculateExtent(data || this.xValues, 'max'); - }; + /** + * Add domain to the x axis scale. + * if time, return a time domain, and calculate the min date, max date, and time interval + * else, return a nominal (d3.scale.ordinal) domain, i.e. array of x axis values + * + * @method getDomain + * @param scale {Function} D3 scale + * @returns {*} D3 scale function + */ + getDomain(scale) { + const ordered = this.ordered; + + if (ordered && ordered.date) { + return this.getTimeDomain(scale, this.xValues); + } + return this.getOrdinalDomain(scale, this.xValues); + }; - /** - * - * @param data - * @param extent - */ - XAxis.prototype._calculateExtent = function (data, extent) { - let ordered = this.ordered; - let opts = [ordered[extent]]; + /** + * Returns D3 time domain + * + * @method getTimeDomain + * @param scale {Function} D3 scale function + * @param data {Array} + * @returns {*} D3 scale function + */ + getTimeDomain(scale, data) { + return scale.domain([this.minExtent(data), this.maxExtent(data)]); + }; - let point = d3[extent](data); - if (this.expandLastBucket && extent === 'max') { - point = this.addInterval(point); - } - opts.push(point); + minExtent(data) { + return this._calculateExtent(data || this.xValues, 'min'); + }; - return d3[extent](opts.reduce(function (opts, v) { - if (!_.isNumber(v)) v = +v; - if (!isNaN(v)) opts.push(v); - return opts; - }, [])); - }; + maxExtent(data) { + return this._calculateExtent(data || this.xValues, 'max'); + }; - /** - * Add the interval to a point on the x axis, - * this properly adds dates if needed. - * - * @param {number} x - a value on the x-axis - * @returns {number} - x + the ordered interval - */ - XAxis.prototype.addInterval = function (x) { - return this.modByInterval(x, +1); - }; + /** + * + * @param data + * @param extent + */ + _calculateExtent(data, extent) { + const ordered = this.ordered; + const opts = [ordered[extent]]; + + let point = d3[extent](data); + if (this.expandLastBucket && extent === 'max') { + point = this.addInterval(point); + } + opts.push(point); - /** - * Subtract the interval to a point on the x axis, - * this properly subtracts dates if needed. - * - * @param {number} x - a value on the x-axis - * @returns {number} - x - the ordered interval - */ - XAxis.prototype.subtractInterval = function (x) { - return this.modByInterval(x, -1); - }; + return d3[extent](opts.reduce(function (opts, v) { + if (!_.isNumber(v)) v = +v; + if (!isNaN(v)) opts.push(v); + return opts; + }, [])); + }; - /** - * Modify the x value by n intervals, properly - * handling dates if needed. - * - * @param {number} x - a value on the x-axis - * @param {number} n - the number of intervals - * @returns {number} - x + n intervals - */ - XAxis.prototype.modByInterval = function (x, n) { - let ordered = this.ordered; - if (!ordered) return x; - let interval = ordered.interval; - if (!interval) return x; - - if (!ordered.date) { - return x += (ordered.interval * n); - } + /** + * Add the interval to a point on the x axis, + * this properly adds dates if needed. + * + * @param {number} x - a value on the x-axis + * @returns {number} - x + the ordered interval + */ + addInterval(x) { + return this.modByInterval(x, +1); + }; - let y = moment(x); - let method = n > 0 ? 'add' : 'subtract'; + /** + * Subtract the interval to a point on the x axis, + * this properly subtracts dates if needed. + * + * @param {number} x - a value on the x-axis + * @returns {number} - x - the ordered interval + */ + subtractInterval(x) { + return this.modByInterval(x, -1); + }; - _.times(Math.abs(n), function () { - y[method](interval); - }); + /** + * Modify the x value by n intervals, properly + * handling dates if needed. + * + * @param {number} x - a value on the x-axis + * @param {number} n - the number of intervals + * @returns {number} - x + n intervals + */ + modByInterval(x, n) { + const ordered = this.ordered; + if (!ordered) return x; + const interval = ordered.interval; + if (!interval) return x; + + if (!ordered.date) { + return x += (ordered.interval * n); + } - return y.valueOf(); - }; + const y = moment(x); + const method = n > 0 ? 'add' : 'subtract'; - /** - * Return a nominal(d3 ordinal) domain - * - * @method getOrdinalDomain - * @param scale {Function} D3 scale function - * @param xValues {Array} Array of x axis values - * @returns {*} D3 scale function - */ - XAxis.prototype.getOrdinalDomain = function (scale, xValues) { - return scale.domain(xValues); - }; + _.times(Math.abs(n), function () { + y[method](interval); + }); - /** - * Return the range for the x axis scale - * if time, return a normal range, else if nominal, return rangeBands with a default (0.1) spacer specified - * - * @method getRange - * @param scale {Function} D3 scale function - * @param width {Number} HTML Element width - * @returns {*} D3 scale function - */ - XAxis.prototype.getRange = function (domain, width) { - let ordered = this.ordered; + return y.valueOf(); + }; - if (ordered && ordered.date) { - return domain.range([0, width]); - } - return domain.rangeBands([0, width], 0.1); - }; + /** + * Return a nominal(d3 ordinal) domain + * + * @method getOrdinalDomain + * @param scale {Function} D3 scale function + * @param xValues {Array} Array of x axis values + * @returns {*} D3 scale function + */ + getOrdinalDomain(scale, xValues) { + return scale.domain(xValues); + }; - /** - * Return the x axis scale - * - * @method getXScale - * @param width {Number} HTML Element width - * @returns {*} D3 x scale function - */ - XAxis.prototype.getXScale = function (width) { - let domain = this.getDomain(this.getScale()); + /** + * Return the range for the x axis scale + * if time, return a normal range, else if nominal, return rangeBands with a default (0.1) spacer specified + * + * @method getRange + * @param scale {Function} D3 scale function + * @param width {Number} HTML Element width + * @returns {*} D3 scale function + */ + getRange(domain, width) { + const ordered = this.ordered; + + if (ordered && ordered.date) { + return domain.range([0, width]); + } + return domain.rangeBands([0, width], 0.1); + }; - return this.getRange(domain, width); - }; + /** + * Return the x axis scale + * + * @method getXScale + * @param width {Number} HTML Element width + * @returns {*} D3 x scale function + */ + getXScale(width) { + const domain = this.getDomain(this.getScale()); + + return this.getRange(domain, width); + }; - /** - * Creates d3 xAxis function - * - * @method getXAxis - * @param width {Number} HTML Element width - */ - XAxis.prototype.getXAxis = function (width) { - this.xScale = this.getXScale(width); + /** + * Creates d3 xAxis function + * + * @method getXAxis + * @param width {Number} HTML Element width + */ + getXAxis(width) { + this.xScale = this.getXScale(width); + + if (!this.xScale || _.isNaN(this.xScale)) { + throw new Error('xScale is ' + this.xScale); + } - if (!this.xScale || _.isNaN(this.xScale)) { - throw new Error('xScale is ' + this.xScale); - } + this.xAxis = d3.svg.axis() + .scale(this.xScale) + .ticks(10) + .tickFormat(this.xAxisFormatter) + .orient('bottom'); + }; - this.xAxis = d3.svg.axis() - .scale(this.xScale) - .ticks(10) - .tickFormat(this.xAxisFormatter) - .orient('bottom'); - }; + /** + * Renders the x axis + * + * @method draw + * @returns {Function} Renders the x axis to a D3 selection + */ + draw() { + const self = this; + this._attr.isRotated = false; - /** - * Renders the x axis - * - * @method draw - * @returns {Function} Renders the x axis to a D3 selection - */ - XAxis.prototype.draw = function () { - let self = this; - let div; - let width; - let height; - let svg; - let parentWidth; - let n; - this._attr.isRotated = false; - - return function (selection) { - n = selection[0].length; - parentWidth = $(self.el) - .find('.x-axis-div-wrapper') - .width(); + return function (selection) { + const n = selection[0].length; + const parentWidth = $(self.el) + .find('.x-axis-div-wrapper') + .width(); - selection.each(function () { + selection.each(function () { - div = d3.select(this); - width = parentWidth / n; - height = $(this.parentElement).height(); + const div = d3.select(this); + const width = parentWidth / n; + const height = $(this.parentElement).height(); - self.validateWidthandHeight(width, height); + self.validateWidthandHeight(width, height); - self.getXAxis(width); + self.getXAxis(width); - svg = div.append('svg') - .attr('width', width) - .attr('height', height); + const svg = div.append('svg') + .attr('width', width) + .attr('height', height); - svg.append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0,0)') - .call(self.xAxis); - }); + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,0)') + .call(self.xAxis); + }); - selection.call(self.filterOrRotate()); + selection.call(self.filterOrRotate()); + }; }; - }; - /** - * Returns a function that evaluates scale type and - * applies filter to tick labels on time scales - * rotates and truncates tick labels on nominal/ordinal scales - * - * @method filterOrRotate - * @returns {Function} Filters or rotates x axis tick labels - */ - XAxis.prototype.filterOrRotate = function () { - let self = this; - let ordered = self.ordered; - let axis; - let labels; + /** + * Returns a function that evaluates scale type and + * applies filter to tick labels on time scales + * rotates and truncates tick labels on nominal/ordinal scales + * + * @method filterOrRotate + * @returns {Function} Filters or rotates x axis tick labels + */ + filterOrRotate() { + const self = this; + const ordered = self.ordered; + + return function (selection) { + selection.each(function () { + const axis = d3.select(this); + if (ordered && ordered.date) { + axis.call(self.filterAxisLabels()); + } else { + axis.call(self.rotateAxisLabels()); + } + }); + + self.updateXaxisHeight(); + + selection.call(self.fitTitles()); + + }; + }; - return function (selection) { - selection.each(function () { - axis = d3.select(this); - labels = axis.selectAll('.tick text'); - if (ordered && ordered.date) { - axis.call(self.filterAxisLabels()); - } else { - axis.call(self.rotateAxisLabels()); + /** + * Rotate the axis tick labels within selection + * + * @returns {Function} Rotates x axis tick labels of a D3 selection + */ + rotateAxisLabels() { + const self = this; + const barWidth = self.xScale.rangeBand(); + const maxRotatedLength = 120; + const xAxisPadding = 15; + const lengths = []; + self._attr.isRotated = false; + + return function (selection) { + const text = selection.selectAll('.tick text'); + + text.each(function textWidths() { + lengths.push(d3.select(this).node().getBBox().width); + }); + const length = _.max(lengths); + self._attr.xAxisLabelHt = length + xAxisPadding; + + // if longer than bar width, rotate + if (length > barWidth) { + self._attr.isRotated = true; } - }); - - self.updateXaxisHeight(); - selection.call(self.fitTitles()); + // if longer than maxRotatedLength, truncate + if (length > maxRotatedLength) { + self._attr.xAxisLabelHt = maxRotatedLength; + } + if (self._attr.isRotated) { + text + .text(function truncate() { + return self.truncateLabel(this, self._attr.xAxisLabelHt); + }) + .style('text-anchor', 'end') + .attr('dx', '-.8em') + .attr('dy', '-.60em') + .attr('transform', function rotate() { + return 'rotate(-90)'; + }) + .append('title') + .text(text => text); + + selection.select('svg') + .attr('height', self._attr.xAxisLabelHt); + } + }; }; - }; - - /** - * Rotate the axis tick labels within selection - * - * @returns {Function} Rotates x axis tick labels of a D3 selection - */ - XAxis.prototype.rotateAxisLabels = function () { - let self = this; - let text; - let barWidth = self.xScale.rangeBand(); - let maxRotatedLength = 120; - let xAxisPadding = 15; - let svg; - let lengths = []; - let length; - self._attr.isRotated = false; - - return function (selection) { - text = selection.selectAll('.tick text'); - - text.each(function textWidths() { - lengths.push(d3.select(this).node().getBBox().width); - }); - length = _.max(lengths); - self._attr.xAxisLabelHt = length + xAxisPadding; - - // if longer than bar width, rotate - if (length > barWidth) { - self._attr.isRotated = true; - } - // if longer than maxRotatedLength, truncate - if (length > maxRotatedLength) { - self._attr.xAxisLabelHt = maxRotatedLength; - } - - if (self._attr.isRotated) { - text - .text(function truncate() { - return self.truncateLabel(this, self._attr.xAxisLabelHt); - }) - .style('text-anchor', 'end') - .attr('dx', '-.8em') - .attr('dy', '-.60em') - .attr('transform', function rotate() { - return 'rotate(-90)'; - }) - .append('title') - .text(text => text); - selection.select('svg') - .attr('height', self._attr.xAxisLabelHt); + /** + * Returns a string that is truncated to fit size + * + * @method truncateLabel + * @param text {HTMLElement} + * @param size {Number} + * @returns {*|jQuery} + */ + truncateLabel(text, size) { + const node = d3.select(text).node(); + let str = $(node).text(); + const width = node.getBBox().width; + const chars = str.length; + const pxPerChar = width / chars; + let endChar = 0; + const ellipsesPad = 4; + + if (width > size) { + endChar = Math.floor((size / pxPerChar) - ellipsesPad); + while (str[endChar - 1] === ' ' || str[endChar - 1] === '-' || str[endChar - 1] === ',') { + endChar = endChar - 1; + } + str = str.substr(0, endChar) + '...'; } + return str; }; - }; - /** - * Returns a string that is truncated to fit size - * - * @method truncateLabel - * @param text {HTMLElement} - * @param size {Number} - * @returns {*|jQuery} - */ - XAxis.prototype.truncateLabel = function (text, size) { - let node = d3.select(text).node(); - let str = $(node).text(); - let width = node.getBBox().width; - let chars = str.length; - let pxPerChar = width / chars; - let endChar = 0; - let ellipsesPad = 4; - - if (width > size) { - endChar = Math.floor((size / pxPerChar) - ellipsesPad); - while (str[endChar - 1] === ' ' || str[endChar - 1] === '-' || str[endChar - 1] === ',') { - endChar = endChar - 1; - } - str = str.substr(0, endChar) + '...'; - } - return str; - }; + /** + * Filter out text labels by width and position on axis + * trims labels that would overlap each other + * or extend past left or right edges + * if prev label pos (or 0) + half of label width is < label pos + * and label pos + half width is not > width of axis + * + * @method filterAxisLabels + * @returns {Function} + */ + filterAxisLabels() { + const self = this; + let startX = 0; + let maxW; + let par; + let myX; + let myWidth; + let halfWidth; + const padding = 1.1; + + return function (selection) { + selection.selectAll('.tick text') + .text(function (d) { + par = d3.select(this.parentNode).node(); + myX = self.xScale(d); + myWidth = par.getBBox().width * padding; + halfWidth = myWidth / 2; + maxW = $(self.el).find('.x-axis-div').width(); + + if ((startX + halfWidth) < myX && maxW > (myX + halfWidth)) { + startX = myX + halfWidth; + return self.xAxisFormatter(d); + } else { + d3.select(this.parentNode).remove(); + } + }); + }; + }; - /** - * Filter out text labels by width and position on axis - * trims labels that would overlap each other - * or extend past left or right edges - * if prev label pos (or 0) + half of label width is < label pos - * and label pos + half width is not > width of axis - * - * @method filterAxisLabels - * @returns {Function} - */ - XAxis.prototype.filterAxisLabels = function () { - let self = this; - let startX = 0; - let maxW; - let par; - let myX; - let myWidth; - let halfWidth; - let padding = 1.1; - - return function (selection) { - selection.selectAll('.tick text') - .text(function (d) { - par = d3.select(this.parentNode).node(); - myX = self.xScale(d); - myWidth = par.getBBox().width * padding; - halfWidth = myWidth / 2; - maxW = $(self.el).find('.x-axis-div').width(); - - if ((startX + halfWidth) < myX && maxW > (myX + halfWidth)) { - startX = myX + halfWidth; - return self.xAxisFormatter(d); - } else { - d3.select(this.parentNode).remove(); - } - }); + /** + * Returns a function that adjusts axis titles and + * chart title transforms to fit axis label divs. + * Sets transform of x-axis-title to fit .x-axis-title div width + * if x-axis-chart-titles, set transform of x-axis-chart-titles + * to fit .chart-title div width + * + * @method fitTitles + * @returns {Function} + */ + fitTitles() { + const visEls = $('.vis-wrapper'); + let xAxisChartTitle; + let yAxisChartTitle; + let text; + let titles; + + return function () { + + visEls.each(function () { + const visEl = d3.select(this); + const $visEl = $(this); + const xAxisTitle = $visEl.find('.x-axis-title'); + const yAxisTitle = $visEl.find('.y-axis-title'); + let titleWidth = xAxisTitle.width(); + let titleHeight = yAxisTitle.height(); + + text = visEl.select('.x-axis-title') + .select('svg') + .attr('width', titleWidth) + .select('text') + .attr('transform', 'translate(' + (titleWidth / 2) + ',11)'); + + text = visEl.select('.y-axis-title') + .select('svg') + .attr('height', titleHeight) + .select('text') + .attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)'); + + if ($visEl.find('.x-axis-chart-title').length) { + xAxisChartTitle = $visEl.find('.x-axis-chart-title'); + titleWidth = xAxisChartTitle.find('.chart-title').width(); + + titles = visEl.select('.x-axis-chart-title').selectAll('.chart-title'); + titles.each(function () { + text = d3.select(this) + .select('svg') + .attr('width', titleWidth) + .select('text') + .attr('transform', 'translate(' + (titleWidth / 2) + ',11)'); + }); + } + + if ($visEl.find('.y-axis-chart-title').length) { + yAxisChartTitle = $visEl.find('.y-axis-chart-title'); + titleHeight = yAxisChartTitle.find('.chart-title').height(); + + titles = visEl.select('.y-axis-chart-title').selectAll('.chart-title'); + titles.each(function () { + text = d3.select(this) + .select('svg') + .attr('height', titleHeight) + .select('text') + .attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)'); + }); + } + + }); + + }; }; - }; - /** - * Returns a function that adjusts axis titles and - * chart title transforms to fit axis label divs. - * Sets transform of x-axis-title to fit .x-axis-title div width - * if x-axis-chart-titles, set transform of x-axis-chart-titles - * to fit .chart-title div width - * - * @method fitTitles - * @returns {Function} - */ - XAxis.prototype.fitTitles = function () { - let visEls = $('.vis-wrapper'); - let xAxisChartTitle; - let yAxisChartTitle; - let text; - let titles; - - return function () { - - visEls.each(function () { - let visEl = d3.select(this); - let $visEl = $(this); - let xAxisTitle = $visEl.find('.x-axis-title'); - let yAxisTitle = $visEl.find('.y-axis-title'); - let titleWidth = xAxisTitle.width(); - let titleHeight = yAxisTitle.height(); - - text = visEl.select('.x-axis-title') - .select('svg') - .attr('width', titleWidth) - .select('text') - .attr('transform', 'translate(' + (titleWidth / 2) + ',11)'); - - text = visEl.select('.y-axis-title') - .select('svg') - .attr('height', titleHeight) - .select('text') - .attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)'); - - if ($visEl.find('.x-axis-chart-title').length) { - xAxisChartTitle = $visEl.find('.x-axis-chart-title'); - titleWidth = xAxisChartTitle.find('.chart-title').width(); - - titles = visEl.select('.x-axis-chart-title').selectAll('.chart-title'); - titles.each(function () { - text = d3.select(this) - .select('svg') - .attr('width', titleWidth) - .select('text') - .attr('transform', 'translate(' + (titleWidth / 2) + ',11)'); - }); - } + /** + * Appends div to make .y-axis-spacer-block + * match height of .x-axis-wrapper + * + * @method updateXaxisHeight + */ + updateXaxisHeight() { + const selection = d3.select(this.el).selectAll('.vis-wrapper'); + + selection.each(function () { + const visEl = d3.select(this); - if ($visEl.find('.y-axis-chart-title').length) { - yAxisChartTitle = $visEl.find('.y-axis-chart-title'); - titleHeight = yAxisChartTitle.find('.chart-title').height(); - - titles = visEl.select('.y-axis-chart-title').selectAll('.chart-title'); - titles.each(function () { - text = d3.select(this) - .select('svg') - .attr('height', titleHeight) - .select('text') - .attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)'); - }); + if (visEl.select('.inner-spacer-block').node() === null) { + visEl.select('.y-axis-spacer-block') + .append('div') + .attr('class', 'inner-spacer-block'); } + const xAxisHt = visEl.select('.x-axis-wrapper').style('height'); + visEl.select('.inner-spacer-block').style('height', xAxisHt); }); }; - }; - - /** - * Appends div to make .y-axis-spacer-block - * match height of .x-axis-wrapper - * - * @method updateXaxisHeight - */ - XAxis.prototype.updateXaxisHeight = function () { - let selection = d3.select(this.el).selectAll('.vis-wrapper'); - - selection.each(function () { - let visEl = d3.select(this); - - if (visEl.select('.inner-spacer-block').node() === null) { - visEl.select('.y-axis-spacer-block') - .append('div') - .attr('class', 'inner-spacer-block'); - } - let xAxisHt = visEl.select('.x-axis-wrapper').style('height'); - - visEl.select('.inner-spacer-block').style('height', xAxisHt); - }); - - }; + } return XAxis; }; diff --git a/src/ui/public/vislib/lib/y_axis.js b/src/ui/public/vislib/lib/y_axis.js index 70eaa7419da3f..f5d7d55ff0e77 100644 --- a/src/ui/public/vislib/lib/y_axis.js +++ b/src/ui/public/vislib/lib/y_axis.js @@ -5,7 +5,7 @@ import errors from 'ui/errors'; import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; export default function YAxisFactory(Private) { - let ErrorHandler = Private(VislibLibErrorHandlerProvider); + const ErrorHandler = Private(VislibLibErrorHandlerProvider); /** * Appends y axis to the visualization @@ -14,221 +14,223 @@ export default function YAxisFactory(Private) { * @constructor * @param args {{el: (HTMLElement), yMax: (Number), _attr: (Object|*)}} */ - _.class(YAxis).inherits(ErrorHandler); - function YAxis(args) { - this.el = args.el; - this.scale = null; - this.domain = [args.yMin, args.yMax]; - this.yAxisFormatter = args.yAxisFormatter; - this._attr = args._attr || {}; - } + class YAxis extends ErrorHandler { + constructor(args) { + super(); + this.el = args.el; + this.scale = null; + this.domain = [args.yMin, args.yMax]; + this.yAxisFormatter = args.yAxisFormatter; + this._attr = args._attr || {}; + } + + /** + * Renders the y axis + * + * @method render + * @return {D3.UpdateSelection} Renders y axis to visualization + */ + render() { + d3.select(this.el).selectAll('.y-axis-div').call(this.draw()); + }; - /** - * Renders the y axis - * - * @method render - * @return {D3.UpdateSelection} Renders y axis to visualization - */ - YAxis.prototype.render = function () { - d3.select(this.el).selectAll('.y-axis-div').call(this.draw()); - }; + _isPercentage() { + return (this._attr.mode === 'percentage'); + }; - YAxis.prototype._isPercentage = function () { - return (this._attr.mode === 'percentage'); - }; + _isUserDefined() { + return (this._attr.setYExtents); + }; - YAxis.prototype._isUserDefined = function () { - return (this._attr.setYExtents); - }; + _isYExtents() { + return (this._attr.defaultYExtents); + }; - YAxis.prototype._isYExtents = function () { - return (this._attr.defaultYExtents); - }; + _validateUserExtents(domain) { + const self = this; - YAxis.prototype._validateUserExtents = function (domain) { - let self = this; + return domain.map(function (val) { + val = parseInt(val, 10); - return domain.map(function (val) { - val = parseInt(val, 10); + if (isNaN(val)) throw new Error(val + ' is not a valid number'); + if (self._isPercentage() && self._attr.setYExtents) return val / 100; + return val; + }); + }; - if (isNaN(val)) throw new Error(val + ' is not a valid number'); - if (self._isPercentage() && self._attr.setYExtents) return val / 100; - return val; - }); - }; + _getExtents(domain) { + const min = domain[0]; + const max = domain[1]; - YAxis.prototype._getExtents = function (domain) { - let min = domain[0]; - let max = domain[1]; + if (this._isUserDefined()) return this._validateUserExtents(domain); + if (this._isYExtents()) return domain; + if (this._attr.scale === 'log') return this._logDomain(min, max); // Negative values cannot be displayed with a log scale. + if (!this._isYExtents() && !this._isUserDefined()) return [Math.min(0, min), Math.max(0, max)]; + return domain; + }; - if (this._isUserDefined()) return this._validateUserExtents(domain); - if (this._isYExtents()) return domain; - if (this._attr.scale === 'log') return this._logDomain(min, max); // Negative values cannot be displayed with a log scale. - if (!this._isYExtents() && !this._isUserDefined()) return [Math.min(0, min), Math.max(0, max)]; - return domain; - }; + _throwCustomError(message) { + throw new Error(message); + }; - YAxis.prototype._throwCustomError = function (message) { - throw new Error(message); - }; + _throwLogScaleValuesError() { + throw new errors.InvalidLogScaleValues(); + }; - YAxis.prototype._throwLogScaleValuesError = function () { - throw new errors.InvalidLogScaleValues(); - }; + /** + * Returns the appropriate D3 scale + * + * @param fnName {String} D3 scale + * @returns {*} + */ + _getScaleType(fnName) { + if (fnName === 'square root') fnName = 'sqrt'; // Rename 'square root' to 'sqrt' + fnName = fnName || 'linear'; - /** - * Returns the appropriate D3 scale - * - * @param fnName {String} D3 scale - * @returns {*} - */ - YAxis.prototype._getScaleType = function (fnName) { - if (fnName === 'square root') fnName = 'sqrt'; // Rename 'square root' to 'sqrt' - fnName = fnName || 'linear'; + if (typeof d3.scale[fnName] !== 'function') return this._throwCustomError('YAxis.getScaleType: ' + fnName + ' is not a function'); - if (typeof d3.scale[fnName] !== 'function') return this._throwCustomError('YAxis.getScaleType: ' + fnName + ' is not a function'); + return d3.scale[fnName](); + }; - return d3.scale[fnName](); - }; + /** + * Return the domain for log scale, i.e. the extent of the log scale. + * Log scales must begin at 1 since the log(0) = -Infinity + * + * @param {Number} min + * @param {Number} max + * @returns {Array} + */ + _logDomain(min, max) { + if (min < 0 || max < 0) return this._throwLogScaleValuesError(); + return [1, max]; + }; - /** - * Return the domain for log scale, i.e. the extent of the log scale. - * Log scales must begin at 1 since the log(0) = -Infinity - * - * @param {Number} min - * @param {Number} max - * @returns {Array} - */ - YAxis.prototype._logDomain = function (min, max) { - if (min < 0 || max < 0) return this._throwLogScaleValuesError(); - return [1, max]; - }; + /** + * Creates the d3 y scale function + * + * @method getYScale + * @param height {Number} DOM Element height + * @returns {D3.Scale.QuantitiveScale|*} D3 yScale function + */ + getYScale(height) { + const scale = this._getScaleType(this._attr.scale); + const domain = this._getExtents(this.domain); + + this.yScale = scale + .domain(domain) + .range([height, 0]); + + if (!this._isUserDefined()) this.yScale.nice(); // round extents when not user defined + // Prevents bars from going off the chart when the y extents are within the domain range + if (this._attr.type === 'histogram') this.yScale.clamp(true); + return this.yScale; + }; - /** - * Creates the d3 y scale function - * - * @method getYScale - * @param height {Number} DOM Element height - * @returns {D3.Scale.QuantitiveScale|*} D3 yScale function - */ - YAxis.prototype.getYScale = function (height) { - let scale = this._getScaleType(this._attr.scale); - let domain = this._getExtents(this.domain); - - this.yScale = scale - .domain(domain) - .range([height, 0]); - - if (!this._isUserDefined()) this.yScale.nice(); // round extents when not user defined - // Prevents bars from going off the chart when the y extents are within the domain range - if (this._attr.type === 'histogram') this.yScale.clamp(true); - return this.yScale; - }; - - YAxis.prototype.getScaleType = function () { - return this._attr.scale; - }; - - YAxis.prototype.tickFormat = function () { - let isPercentage = this._attr.mode === 'percentage'; - if (isPercentage) return d3.format('%'); - if (this.yAxisFormatter) return this.yAxisFormatter; - return d3.format('n'); - }; - - YAxis.prototype._validateYScale = function (yScale) { - if (!yScale || _.isNaN(yScale)) throw new Error('yScale is ' + yScale); - }; + getScaleType() { + return this._attr.scale; + }; - /** - * Creates the d3 y axis function - * - * @method getYAxis - * @param height {Number} DOM Element height - * @returns {D3.Svg.Axis|*} D3 yAxis function - */ - YAxis.prototype.getYAxis = function (height) { - let yScale = this.getYScale(height); - this._validateYScale(yScale); + tickFormat() { + const isPercentage = this._attr.mode === 'percentage'; + if (isPercentage) return d3.format('%'); + if (this.yAxisFormatter) return this.yAxisFormatter; + return d3.format('n'); + }; - // Create the d3 yAxis function - this.yAxis = d3.svg.axis() + _validateYScale(yScale) { + if (!yScale || _.isNaN(yScale)) throw new Error('yScale is ' + yScale); + }; + + /** + * Creates the d3 y axis function + * + * @method getYAxis + * @param height {Number} DOM Element height + * @returns {D3.Svg.Axis|*} D3 yAxis function + */ + getYAxis(height) { + const yScale = this.getYScale(height); + this._validateYScale(yScale); + + // Create the d3 yAxis function + this.yAxis = d3.svg.axis() .scale(yScale) .tickFormat(this.tickFormat(this.domain)) .ticks(this.tickScale(height)) .orient('left'); - return this.yAxis; - }; - - /** - * Create a tick scale for the y axis that modifies the number of ticks - * based on the height of the wrapping DOM element - * Avoid using even numbers in the yTickScale.range - * Causes the top most tickValue in the chart to be missing - * - * @method tickScale - * @param height {Number} DOM element height - * @returns {number} Number of y axis ticks - */ - YAxis.prototype.tickScale = function (height) { - let yTickScale = d3.scale.linear() - .clamp(true) - .domain([20, 40, 1000]) - .range([0, 3, 11]); + return this.yAxis; + }; - return Math.ceil(yTickScale(height)); - }; + /** + * Create a tick scale for the y axis that modifies the number of ticks + * based on the height of the wrapping DOM element + * Avoid using even numbers in the yTickScale.range + * Causes the top most tickValue in the chart to be missing + * + * @method tickScale + * @param height {Number} DOM element height + * @returns {number} Number of y axis ticks + */ + tickScale(height) { + const yTickScale = d3.scale.linear() + .clamp(true) + .domain([20, 40, 1000]) + .range([0, 3, 11]); + + return Math.ceil(yTickScale(height)); + }; - /** - * Renders the y axis to the visualization - * - * @method draw - * @returns {Function} Renders y axis to visualization - */ - YAxis.prototype.draw = function () { - let self = this; - let margin = this._attr.margin; - let mode = this._attr.mode; - let isWiggleOrSilhouette = (mode === 'wiggle' || mode === 'silhouette'); - - return function (selection) { - selection.each(function () { - let el = this; - - let div = d3.select(el); - let width = $(el).parent().width(); - let height = $(el).height(); - let adjustedHeight = height - margin.top - margin.bottom; - - // Validate whether width and height are not 0 or `NaN` - self.validateWidthandHeight(width, adjustedHeight); - - let yAxis = self.getYAxis(adjustedHeight); - - // The yAxis should not appear if mode is set to 'wiggle' or 'silhouette' - if (!isWiggleOrSilhouette) { - // Append svg and y axis - let svg = div.append('svg') - .attr('width', width) - .attr('height', height); - - svg.append('g') - .attr('class', 'y axis') - .attr('transform', 'translate(' + (width - 2) + ',' + margin.top + ')') - .call(yAxis); - - let container = svg.select('g.y.axis').node(); - if (container) { - let cWidth = Math.max(width, container.getBBox().width); - svg.attr('width', cWidth); - svg.select('g') - .attr('transform', 'translate(' + (cWidth - 2) + ',' + margin.top + ')'); + /** + * Renders the y axis to the visualization + * + * @method draw + * @returns {Function} Renders y axis to visualization + */ + draw() { + const self = this; + const margin = this._attr.margin; + const mode = this._attr.mode; + const isWiggleOrSilhouette = (mode === 'wiggle' || mode === 'silhouette'); + + return function (selection) { + selection.each(function () { + const el = this; + + const div = d3.select(el); + const width = $(el).parent().width(); + const height = $(el).height(); + const adjustedHeight = height - margin.top - margin.bottom; + + // Validate whether width and height are not 0 or `NaN` + self.validateWidthandHeight(width, adjustedHeight); + + const yAxis = self.getYAxis(adjustedHeight); + + // The yAxis should not appear if mode is set to 'wiggle' or 'silhouette' + if (!isWiggleOrSilhouette) { + // Append svg and y axis + const svg = div.append('svg') + .attr('width', width) + .attr('height', height); + + svg.append('g') + .attr('class', 'y axis') + .attr('transform', 'translate(' + (width - 2) + ',' + margin.top + ')') + .call(yAxis); + + const container = svg.select('g.y.axis').node(); + if (container) { + const cWidth = Math.max(width, container.getBBox().width); + svg.attr('width', cWidth); + svg.select('g') + .attr('transform', 'translate(' + (cWidth - 2) + ',' + margin.top + ')'); + } } - } - }); + }); + }; }; - }; + } return YAxis; }; diff --git a/src/ui/public/vislib/vis.js b/src/ui/public/vislib/vis.js index 0c45ac54baaa7..9b08e5aa5c22a 100644 --- a/src/ui/public/vislib/vis.js +++ b/src/ui/public/vislib/vis.js @@ -10,10 +10,10 @@ import VislibVisualizationsVisTypesProvider from 'ui/vislib/visualizations/vis_t export default function VisFactory(Private) { - let ResizeChecker = Private(VislibLibResizeCheckerProvider); - let Events = Private(EventsProvider); - let handlerTypes = Private(VislibLibHandlerHandlerTypesProvider); - let chartTypes = Private(VislibVisualizationsVisTypesProvider); + const ResizeChecker = Private(VislibLibResizeCheckerProvider); + const Events = Private(EventsProvider); + const handlerTypes = Private(VislibLibHandlerHandlerTypesProvider); + const chartTypes = Private(VislibVisualizationsVisTypesProvider); /** * Creates the visualizations. @@ -23,172 +23,169 @@ export default function VisFactory(Private) { * @param $el {HTMLElement} jQuery selected HTML element * @param config {Object} Parameters that define the chart type and chart options */ - _.class(Vis).inherits(Events); - function Vis($el, config) { - if (!(this instanceof Vis)) { - return new Vis($el, config); - } - Vis.Super.apply(this, arguments); - this.el = $el.get ? $el.get(0) : $el; - this.binder = new Binder(); - this.ChartClass = chartTypes[config.type]; - this._attr = _.defaults({}, config || {}, { - legendOpen: true - }); - - // bind the resize function so it can be used as an event handler - this.resize = _.bind(this.resize, this); - this.resizeChecker = new ResizeChecker(this.el); - this.binder.on(this.resizeChecker, 'resize', this.resize); - } - - /** - * Renders the visualization - * - * @method render - * @param data {Object} Elasticsearch query results - */ - Vis.prototype.render = function (data, uiState) { - let chartType = this._attr.type; - - if (!data) { - throw new Error('No valid data!'); + class Vis extends Events { + constructor($el, config) { + super(arguments); + this.el = $el.get ? $el.get(0) : $el; + this.binder = new Binder(); + this.ChartClass = chartTypes[config.type]; + this._attr = _.defaults({}, config || {}, { + legendOpen: true + }); + + // bind the resize function so it can be used as an event handler + this.resize = _.bind(this.resize, this); + this.resizeChecker = new ResizeChecker(this.el); + this.binder.on(this.resizeChecker, 'resize', this.resize); } - if (this.handler) { - this.data = null; - this._runOnHandler('destroy'); - } + /** + * Renders the visualization + * + * @method render + * @param data {Object} Elasticsearch query results + */ + render(data, uiState) { + const chartType = this._attr.type; + + if (!data) { + throw new Error('No valid data!'); + } - this.data = data; + if (this.handler) { + this.data = null; + this._runOnHandler('destroy'); + } - if (!this.uiState) { - this.uiState = uiState; - uiState.on('change', this._uiStateChangeHandler = () => this.render(this.data, this.uiState)); - } + this.data = data; - this.handler = handlerTypes[chartType](this) || handlerTypes.column(this); - this._runWithoutResizeChecker('render'); - }; + if (!this.uiState) { + this.uiState = uiState; + uiState.on('change', this._uiStateChangeHandler = () => this.render(this.data, this.uiState)); + } - /** - * Resizes the visualization - * - * @method resize - */ - Vis.prototype.resize = function () { - if (!this.data) { - // TODO: need to come up with a solution for resizing when no data is available - return; - } + this.handler = handlerTypes[chartType](this) || handlerTypes.column(this); + this._runWithoutResizeChecker('render'); + }; + + /** + * Resizes the visualization + * + * @method resize + */ + resize() { + if (!this.data) { + // TODO: need to come up with a solution for resizing when no data is available + return; + } - if (this.handler && _.isFunction(this.handler.resize)) { - this._runOnHandler('resize'); - } else { - this.render(this.data, this.uiState); - } - }; - - Vis.prototype._runWithoutResizeChecker = function (method) { - this.resizeChecker.stopSchedule(); - this._runOnHandler(method); - this.resizeChecker.saveSize(); - this.resizeChecker.saveDirty(false); - this.resizeChecker.continueSchedule(); - }; - - Vis.prototype._runOnHandler = function (method) { - try { - this.handler[method](); - } catch (error) { - - if (error instanceof errors.KbnError) { - error.displayToScreen(this.handler); + if (this.handler && _.isFunction(this.handler.resize)) { + this._runOnHandler('resize'); } else { - throw error; + this.render(this.data, this.uiState); } + }; + + _runWithoutResizeChecker(method) { + this.resizeChecker.stopSchedule(); + this._runOnHandler(method); + this.resizeChecker.saveSize(); + this.resizeChecker.saveDirty(false); + this.resizeChecker.continueSchedule(); + }; + + _runOnHandler(method) { + try { + this.handler[method](); + } catch (error) { + + if (error instanceof errors.KbnError) { + error.displayToScreen(this.handler); + } else { + throw error; + } - } - }; - - /** - * Destroys the visualization - * Removes chart and all elements associated with it. - * Removes chart and all elements associated with it. - * Remove event listeners and pass destroy call down to owned objects. - * - * @method destroy - */ - Vis.prototype.destroy = function () { - let selection = d3.select(this.el).select('.vis-wrapper'); - - this.binder.destroy(); - this.resizeChecker.destroy(); - if (this.uiState) this.uiState.off('change', this._uiStateChangeHandler); - if (this.handler) this._runOnHandler('destroy'); - - selection.remove(); - selection = null; - }; - - /** - * Sets attributes on the visualization - * - * @method set - * @param name {String} An attribute name - * @param val {*} Value to which the attribute name is set - */ - Vis.prototype.set = function (name, val) { - this._attr[name] = val; - this.render(this.data, this.uiState); - }; - - /** - * Gets attributes from the visualization - * - * @method get - * @param name {String} An attribute name - * @returns {*} The value of the attribute name - */ - Vis.prototype.get = function (name) { - return this._attr[name]; - }; - - /** - * Turns on event listeners. - * - * @param event {String} - * @param listener{Function} - * @returns {*} - */ - Vis.prototype.on = function (event, listener) { - let first = this.listenerCount(event) === 0; - let ret = Events.prototype.on.call(this, event, listener); - let added = this.listenerCount(event) > 0; - - // if this is the first listener added for the event - // enable the event in the handler - if (first && added && this.handler) this.handler.enable(event); - - return ret; - }; - - /** - * Turns off event listeners. - * - * @param event {String} - * @param listener{Function} - * @returns {*} - */ - Vis.prototype.off = function (event, listener) { - let last = this.listenerCount(event) === 1; - let ret = Events.prototype.off.call(this, event, listener); - let removed = this.listenerCount(event) === 0; - - // Once all listeners are removed, disable the events in the handler - if (last && removed && this.handler) this.handler.disable(event); - return ret; - }; + } + }; + + /** + * Destroys the visualization + * Removes chart and all elements associated with it. + * Removes chart and all elements associated with it. + * Remove event listeners and pass destroy call down to owned objects. + * + * @method destroy + */ + destroy() { + const selection = d3.select(this.el).select('.vis-wrapper'); + + this.binder.destroy(); + this.resizeChecker.destroy(); + if (this.uiState) this.uiState.off('change', this._uiStateChangeHandler); + if (this.handler) this._runOnHandler('destroy'); + + selection.remove(); + }; + + /** + * Sets attributes on the visualization + * + * @method set + * @param name {String} An attribute name + * @param val {*} Value to which the attribute name is set + */ + set(name, val) { + this._attr[name] = val; + this.render(this.data, this.uiState); + }; + + /** + * Gets attributes from the visualization + * + * @method get + * @param name {String} An attribute name + * @returns {*} The value of the attribute name + */ + get(name) { + return this._attr[name]; + }; + + /** + * Turns on event listeners. + * + * @param event {String} + * @param listener{Function} + * @returns {*} + */ + on(event, listener) { + const first = this.listenerCount(event) === 0; + const ret = Events.prototype.on.call(this, event, listener); + const added = this.listenerCount(event) > 0; + + // if this is the first listener added for the event + // enable the event in the handler + if (first && added && this.handler) this.handler.enable(event); + + return ret; + }; + + /** + * Turns off event listeners. + * + * @param event {String} + * @param listener{Function} + * @returns {*} + */ + off(event, listener) { + const last = this.listenerCount(event) === 1; + const ret = Events.prototype.off.call(this, event, listener); + const removed = this.listenerCount(event) === 0; + + // Once all listeners are removed, disable the events in the handler + if (last && removed && this.handler) this.handler.disable(event); + return ret; + }; + } return Vis; }; diff --git a/src/ui/public/vislib/visualizations/_chart.js b/src/ui/public/vislib/visualizations/_chart.js index 4286df6b16279..7f9248b08c3fd 100644 --- a/src/ui/public/vislib/visualizations/_chart.js +++ b/src/ui/public/vislib/visualizations/_chart.js @@ -1,13 +1,12 @@ import d3 from 'd3'; import _ from 'lodash'; -import errors from 'ui/errors'; import dataLabel from 'ui/vislib/lib/_data_label'; import VislibLibDispatchProvider from 'ui/vislib/lib/dispatch'; import VislibComponentsTooltipProvider from 'ui/vislib/components/tooltip'; export default function ChartBaseClass(Private) { - let Dispatch = Private(VislibLibDispatchProvider); - let Tooltip = Private(VislibComponentsTooltipProvider); + const Dispatch = Private(VislibLibDispatchProvider); + const Tooltip = Private(VislibComponentsTooltipProvider); /** * The Base Class for all visualizations. @@ -18,80 +17,77 @@ export default function ChartBaseClass(Private) { * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ - function Chart(handler, el, chartData) { - if (!(this instanceof Chart)) { - return new Chart(handler, el, chartData); - } + class Chart { + constructor(handler, el, chartData) { + this.handler = handler; + this.chartEl = el; + this.chartData = chartData; + this.tooltips = []; - this.handler = handler; - this.chartEl = el; - this.chartData = chartData; - this.tooltips = []; + const events = this.events = new Dispatch(handler); - let events = this.events = new Dispatch(handler); + if (_.get(this.handler, '_attr.addTooltip')) { + const $el = this.handler.el; + const formatter = this.handler.data.get('tooltipFormatter'); - if (_.get(this.handler, '_attr.addTooltip')) { - let $el = this.handler.el; - let formatter = this.handler.data.get('tooltipFormatter'); + // Add tooltip + this.tooltip = new Tooltip('chart', $el, formatter, events); + this.tooltips.push(this.tooltip); + } - // Add tooltip - this.tooltip = new Tooltip('chart', $el, formatter, events); - this.tooltips.push(this.tooltip); + this._attr = _.defaults(this.handler._attr || {}, {}); + this._addIdentifier = _.bind(this._addIdentifier, this); } - this._attr = _.defaults(this.handler._attr || {}, {}); - this._addIdentifier = _.bind(this._addIdentifier, this); - } + /** + * Renders the chart(s) + * + * @method render + * @returns {HTMLElement} Contains the D3 chart + */ + render() { + const selection = d3.select(this.chartEl); - /** - * Renders the chart(s) - * - * @method render - * @returns {HTMLElement} Contains the D3 chart - */ - Chart.prototype.render = function () { - let selection = d3.select(this.chartEl); - - selection.selectAll('*').remove(); - selection.call(this.draw()); - }; + selection.selectAll('*').remove(); + selection.call(this.draw()); + }; - /** - * Append the data label to the element - * - * @method _addIdentifier - * @param selection {Object} d3 select object - */ - Chart.prototype._addIdentifier = function (selection, labelProp) { - labelProp = labelProp || 'label'; - let labels = this.handler.data.labels; + /** + * Append the data label to the element + * + * @method _addIdentifier + * @param selection {Object} d3 select object + */ + _addIdentifier(selection, labelProp) { + labelProp = labelProp || 'label'; + const labels = this.handler.data.labels; - function resolveLabel(datum) { - if (labels.length === 1) return labels[0]; - if (datum[0]) return datum[0][labelProp]; - return datum[labelProp]; - } + function resolveLabel(datum) { + if (labels.length === 1) return labels[0]; + if (datum[0]) return datum[0][labelProp]; + return datum[labelProp]; + } - selection.each(function (datum) { - let label = resolveLabel(datum); - if (label != null) dataLabel(this, label); - }); - }; + selection.each(function (datum) { + const label = resolveLabel(datum); + if (label != null) dataLabel(this, label); + }); + }; - /** - * Removes all DOM elements from the root element - * - * @method destroy - */ - Chart.prototype.destroy = function () { - let selection = d3.select(this.chartEl); - this.events.removeAllListeners(); - this.tooltips.forEach(function (tooltip) { - tooltip.destroy(); - }); - selection.remove(); - selection = null; - }; + /** + * Removes all DOM elements from the root element + * + * @method destroy + */ + destroy() { + const selection = d3.select(this.chartEl); + this.events.removeAllListeners(); + this.tooltips.forEach(function (tooltip) { + tooltip.destroy(); + }); + selection.remove(); + }; + } return Chart; }; diff --git a/src/ui/public/vislib/visualizations/_map.js b/src/ui/public/vislib/visualizations/_map.js index b61de613488cd..1b138c338a33f 100644 --- a/src/ui/public/vislib/visualizations/_map.js +++ b/src/ui/public/vislib/visualizations/_map.js @@ -13,19 +13,19 @@ import VislibVisualizationsMarkerTypesGeohashGridProvider from 'ui/vislib/visual import VislibVisualizationsMarkerTypesHeatmapProvider from 'ui/vislib/visualizations/marker_types/heatmap'; export default function MapFactory(Private, tilemap, $sanitize) { - let defaultMapZoom = 2; - let defaultMapCenter = [15, 5]; - let defaultMarkerType = 'Scaled Circle Markers'; + const defaultMapZoom = 2; + const defaultMapCenter = [15, 5]; + const defaultMarkerType = 'Scaled Circle Markers'; - let tilemapOptions = tilemap.options; - let attribution = $sanitize(marked(tilemapOptions.attribution)); + const tilemapOptions = tilemap.options; + const attribution = $sanitize(marked(tilemapOptions.attribution)); - let mapTiles = { + const mapTiles = { url: tilemap.url, options: _.assign({}, tilemapOptions, { attribution }) }; - let markerTypes = { + const markerTypes = { 'Scaled Circle Markers': Private(VislibVisualizationsMarkerTypesScaledCirclesProvider), 'Shaded Circle Markers': Private(VislibVisualizationsMarkerTypesShadedCirclesProvider), 'Shaded Geohash Grid': Private(VislibVisualizationsMarkerTypesGeohashGridProvider), @@ -41,285 +41,287 @@ export default function MapFactory(Private, tilemap, $sanitize) { * @param chartData {Object} Elasticsearch query results for this map * @param params {Object} Parameters used to build a map */ - function TileMapMap(container, chartData, params) { - this._container = $(container).get(0); - this._chartData = chartData; - - // keep a reference to all of the optional params - this._events = _.get(params, 'events'); - this._markerType = markerTypes[params.markerType] ? params.markerType : defaultMarkerType; - this._valueFormatter = params.valueFormatter || _.identity; - this._tooltipFormatter = params.tooltipFormatter || _.identity; - this._geoJson = _.get(this._chartData, 'geoJson'); - this._mapZoom = Math.max(Math.min(params.zoom || defaultMapZoom, tilemapOptions.maxZoom), tilemapOptions.minZoom); - this._mapCenter = params.center || defaultMapCenter; - this._attr = params.attr || {}; - - let mapOptions = { - minZoom: tilemapOptions.minZoom, - maxZoom: tilemapOptions.maxZoom, - noWrap: true, - maxBounds: L.latLngBounds([-90, -220], [90, 220]), - scrollWheelZoom: false, - fadeAnimation: false, - }; - - this._createMap(mapOptions); - } + class TileMapMap { + constructor(container, chartData, params) { + this._container = $(container).get(0); + this._chartData = chartData; + + // keep a reference to all of the optional params + this._events = _.get(params, 'events'); + this._markerType = markerTypes[params.markerType] ? params.markerType : defaultMarkerType; + this._valueFormatter = params.valueFormatter || _.identity; + this._tooltipFormatter = params.tooltipFormatter || _.identity; + this._geoJson = _.get(this._chartData, 'geoJson'); + this._mapZoom = Math.max(Math.min(params.zoom || defaultMapZoom, tilemapOptions.maxZoom), tilemapOptions.minZoom); + this._mapCenter = params.center || defaultMapCenter; + this._attr = params.attr || {}; + + const mapOptions = { + minZoom: tilemapOptions.minZoom, + maxZoom: tilemapOptions.maxZoom, + noWrap: true, + maxBounds: L.latLngBounds([-90, -220], [90, 220]), + scrollWheelZoom: false, + fadeAnimation: false, + }; + + this._createMap(mapOptions); + } - TileMapMap.prototype.addBoundingControl = function () { - if (this._boundingControl) return; + addBoundingControl() { + if (this._boundingControl) return; + + const self = this; + const drawOptions = {draw: {}}; + + _.each(['polyline', 'polygon', 'circle', 'marker', 'rectangle'], function (drawShape) { + if (self._events && !self._events.listenerCount(drawShape)) { + drawOptions.draw[drawShape] = false; + } else { + drawOptions.draw[drawShape] = { + shapeOptions: { + stroke: false, + color: '#000' + } + }; + } + }); - let self = this; - let drawOptions = { draw: {} }; + this._boundingControl = new L.Control.Draw(drawOptions); + this.map.addControl(this._boundingControl); + }; - _.each(['polyline', 'polygon', 'circle', 'marker', 'rectangle'], function (drawShape) { - if (self._events && !self._events.listenerCount(drawShape)) { - drawOptions.draw[drawShape] = false; - } else { - drawOptions.draw[drawShape] = { - shapeOptions: { - stroke: false, - color: '#000' - } - }; - } - }); + addFitControl() { + if (this._fitControl) return; + + const self = this; + const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit'); + + // Add button to fit container to points + const FitControl = L.Control.extend({ + options: { + position: 'topleft' + }, + onAdd: function () { + $(fitContainer).html('') + .on('click', function (e) { + e.preventDefault(); + self._fitBounds(); + }); + + return fitContainer; + }, + onRemove: function () { + $(fitContainer).off('click'); + } + }); - this._boundingControl = new L.Control.Draw(drawOptions); - this.map.addControl(this._boundingControl); - }; + this._fitControl = new FitControl(); + this.map.addControl(this._fitControl); + }; - TileMapMap.prototype.addFitControl = function () { - if (this._fitControl) return; - - let self = this; - let fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit'); - - // Add button to fit container to points - let FitControl = L.Control.extend({ - options: { - position: 'topleft' - }, - onAdd: function (map) { - $(fitContainer).html('') - .on('click', function (e) { - e.preventDefault(); - self._fitBounds(); - }); + /** + * Adds label div to each map when data is split + * + * @method addTitle + * @param mapLabel {String} + * @return {undefined} + */ + addTitle(mapLabel) { + if (this._label) return; + + const label = this._label = L.control(); + + label.onAdd = function () { + this._div = L.DomUtil.create('div', 'tilemap-info tilemap-label'); + this.update(); + return this._div; + }; + label.update = function () { + this._div.innerHTML = '