From 47911d6fc5c065712e36b80a95dc4a8eacf81892 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Thu, 22 Sep 2016 17:47:41 +0200 Subject: [PATCH] Backport PR #8186 --------- **Commit 1:** converting to ES6 class syntax * Original sha: 161ba75d46f0d8555da00a91a20549468357ba67 * Authored by ppisljar on 2016-09-08T14:28:44Z **Commit 2:** let to const * Original sha: 9cb6b60fe062cd325ee61f10b254125406619755 * Authored by ppisljar on 2016-09-08T14:54:41Z **Commit 3:** fixing indentation to match our style * Original sha: 0b63b830f2e445401c5fc511c18ae6cc11b0d864 * Authored by ppisljar on 2016-09-08T15:08:56Z **Commit 4:** removing unused variables/imports * Original sha: 93295012fc19a34fc0aad31c3a8971e69217b7ae * Authored by ppisljar on 2016-09-08T15:23:24Z --- .../vislib/__tests__/components/color.js | 32 +- .../vislib/__tests__/components/labels.js | 54 +- .../__tests__/components/zero_injection.js | 38 +- .../public/vislib/__tests__/lib/axis_title.js | 2 +- .../vislib/__tests__/lib/chart_title.js | 2 +- src/ui/public/vislib/__tests__/lib/data.js | 68 +- .../public/vislib/__tests__/lib/dispatch.js | 18 +- .../vislib/__tests__/lib/handler/handler.js | 8 +- .../vislib/__tests__/lib/layout/layout.js | 4 +- .../lib/layout/splits/column_chart/splits.js | 2 +- .../lib/layout/types/column_layout.js | 2 +- .../vislib/__tests__/lib/resize_checker.js | 30 +- src/ui/public/vislib/__tests__/lib/x_axis.js | 2 +- src/ui/public/vislib/__tests__/lib/y_axis.js | 64 +- src/ui/public/vislib/__tests__/vis.js | 19 +- .../__tests__/visualizations/area_chart.js | 21 +- .../vislib/__tests__/visualizations/chart.js | 4 +- .../__tests__/visualizations/column_chart.js | 36 +- .../__tests__/visualizations/line_chart.js | 16 +- .../__tests__/visualizations/pie_chart.js | 30 +- .../__tests__/visualizations/tile_maps/map.js | 42 +- .../visualizations/tile_maps/markers.js | 128 +- .../visualizations/tile_maps/tile_map.js | 20 +- .../__tests__/visualizations/time_marker.js | 18 +- .../public/vislib/components/color/color.js | 2 +- .../vislib/components/color/color_palette.js | 14 +- .../vislib/components/color/mapped_colors.js | 2 +- .../vislib/components/labels/data_array.js | 2 +- .../public/vislib/components/labels/labels.js | 6 +- .../components/labels/pie/get_pie_names.js | 4 +- .../components/labels/pie/pie_labels.js | 10 +- .../components/labels/pie/return_pie_names.js | 2 +- .../tooltip/__tests__/positioning.js | 44 +- .../components/tooltip/position_tooltip.js | 36 +- .../vislib/components/tooltip/tooltip.js | 32 +- .../components/zero_injection/inject_zeros.js | 12 +- .../zero_injection/ordered_x_keys.js | 16 +- .../components/zero_injection/uniq_keys.js | 14 +- .../zero_injection/zero_fill_data_array.js | 4 +- .../zero_injection/zero_filled_array.js | 2 +- src/ui/public/vislib/lib/_error_handler.js | 38 +- src/ui/public/vislib/lib/alerts.js | 161 ++- src/ui/public/vislib/lib/axis_title.js | 99 +- src/ui/public/vislib/lib/chart_title.js | 211 ++- src/ui/public/vislib/lib/data.js | 1230 ++++++++--------- src/ui/public/vislib/lib/dispatch.js | 524 ++++--- src/ui/public/vislib/lib/handler/handler.js | 339 +++-- .../vislib/lib/handler/handler_types.js | 2 +- src/ui/public/vislib/lib/handler/types/pie.js | 6 +- .../vislib/lib/handler/types/point_series.js | 37 +- .../vislib/lib/handler/types/tile_map.js | 9 +- src/ui/public/vislib/lib/layout/layout.js | 244 ++-- .../layout/splits/column_chart/chart_split.js | 4 +- .../splits/column_chart/chart_title_split.js | 6 +- .../splits/column_chart/x_axis_split.js | 2 +- .../splits/column_chart/y_axis_split.js | 12 +- .../layout/splits/pie_chart/chart_split.js | 4 +- .../splits/pie_chart/chart_title_split.js | 2 +- .../lib/layout/splits/tile_map/map_split.js | 4 +- .../vislib/lib/layout/types/column_layout.js | 9 +- .../vislib/lib/layout/types/map_layout.js | 3 +- .../vislib/lib/layout/types/pie_layout.js | 5 +- src/ui/public/vislib/lib/resize_checker.js | 28 +- src/ui/public/vislib/lib/x_axis.js | 903 ++++++------ src/ui/public/vislib/lib/y_axis.js | 380 ++--- src/ui/public/vislib/vis.js | 317 +++-- src/ui/public/vislib/visualizations/_chart.js | 128 +- src/ui/public/vislib/visualizations/_map.js | 510 +++---- .../visualizations/_point_series_chart.js | 296 ++-- .../vislib/visualizations/area_chart.js | 638 +++++---- .../vislib/visualizations/column_chart.js | 579 ++++---- .../vislib/visualizations/line_chart.js | 564 ++++---- .../marker_types/base_marker.js | 448 +++--- .../marker_types/geohash_grid.js | 39 +- .../visualizations/marker_types/heatmap.js | 337 +++-- .../marker_types/scaled_circles.js | 82 +- .../marker_types/shaded_circles.js | 84 +- .../public/vislib/visualizations/pie_chart.js | 313 ++--- .../public/vislib/visualizations/tile_map.js | 199 ++- .../vislib/visualizations/time_marker.js | 74 +- 80 files changed, 4817 insertions(+), 4915 deletions(-) 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 = $('
').css({ + const node = $('
').css({ height: 40, width: 40 }) @@ -72,7 +72,7 @@ function createData(seriesData) { yAxisDiv = el.append('div') .attr('class', 'y-axis-div'); - let dataObj = new Data(data, { + const dataObj = new Data(data, { defaultYMin: true }, persistedState); @@ -133,10 +133,10 @@ describe('Vislib yAxis Class Test Suite', function () { let yScale; let graphData; let domain; - let height = 50; + const height = 50; function checkDomain(min, max) { - let domain = yScale.domain(); + const domain = yScale.domain(); expect(domain[0]).to.be.lessThan(min + 1); expect(domain[1]).to.be.greaterThan(max - 1); return domain; @@ -181,9 +181,9 @@ describe('Vislib yAxis Class Test Suite', function () { it('should have domain between 0 and max value', function () { - let min = 0; - let max = _.max(_.flattenDeep(graphData)); - let domain = checkDomain(min, max); + const min = 0; + const max = _.max(_.flattenDeep(graphData)); + const domain = checkDomain(min, max); expect(domain[1]).to.be.greaterThan(0); checkRange(); }); @@ -200,9 +200,9 @@ describe('Vislib yAxis Class Test Suite', function () { }); it('should have domain between min value and 0', function () { - let min = _.min(_.flattenDeep(graphData)); - let max = 0; - let domain = checkDomain(min, max); + const min = _.min(_.flattenDeep(graphData)); + const max = 0; + const domain = checkDomain(min, max); expect(domain[0]).to.be.lessThan(0); checkRange(); }); @@ -219,9 +219,9 @@ describe('Vislib yAxis Class Test Suite', function () { }); it('should have domain between min and max values', function () { - let min = _.min(_.flattenDeep(graphData)); - let max = _.max(_.flattenDeep(graphData)); - let domain = checkDomain(min, max); + const min = _.min(_.flattenDeep(graphData)); + const max = _.max(_.flattenDeep(graphData)); + const domain = checkDomain(min, max); expect(domain[0]).to.be.lessThan(0); expect(domain[1]).to.be.greaterThan(0); checkRange(); @@ -236,8 +236,8 @@ describe('Vislib yAxis Class Test Suite', function () { }); it('should throw a NaN error', function () { - let min = 'Not a number'; - let max = 12; + const min = 'Not a number'; + const max = 12; expect(function () { yAxis._validateUserExtents(min, max); @@ -250,7 +250,7 @@ describe('Vislib yAxis Class Test Suite', function () { domain = []; domain[0] = yAxis._attr.yAxis.min = 20; domain[1] = yAxis._attr.yAxis.max = 80; - let newDomain = yAxis._validateUserExtents(domain); + const newDomain = yAxis._validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0] / 100); expect(newDomain[1]).to.be(domain[1] / 100); @@ -258,7 +258,7 @@ describe('Vislib yAxis Class Test Suite', function () { it('should return the user defined value', function () { domain = [20, 50]; - let newDomain = yAxis._validateUserExtents(domain); + const newDomain = yAxis._validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0]); expect(newDomain[1]).to.be(domain[1]); @@ -267,8 +267,8 @@ describe('Vislib yAxis Class Test Suite', function () { describe('should throw an error when', function () { it('min === max', function () { - let min = 12; - let max = 12; + const min = 12; + const max = 12; expect(function () { yAxis._validateAxisExtents(min, max); @@ -276,8 +276,8 @@ describe('Vislib yAxis Class Test Suite', function () { }); it('min > max', function () { - let min = 30; - let max = 10; + const min = 30; + const max = 10; expect(function () { yAxis._validateAxisExtents(min, max); @@ -287,7 +287,7 @@ describe('Vislib yAxis Class Test Suite', function () { }); describe('getScaleType method', function () { - let fnNames = ['linear', 'log', 'square root']; + const fnNames = ['linear', 'log', 'square root']; it('should return a function', function () { fnNames.forEach(function (fnName) { @@ -319,7 +319,7 @@ describe('Vislib yAxis Class Test Suite', function () { }); it('should return a yMin value of 1', function () { - let yMin = yAxis._logDomain(0, 200)[0]; + const yMin = yAxis._logDomain(0, 200)[0]; expect(yMin).to.be(1); }); }); @@ -343,13 +343,13 @@ describe('Vislib yAxis Class Test Suite', function () { it('should use percentage format for percentages', function () { yAxis._attr.mode = 'percentage'; - let tickFormat = yAxis.getYAxis().tickFormat(); + const tickFormat = yAxis.getYAxis().tickFormat(); expect(tickFormat(1)).to.be('100%'); }); it('should use decimal format for small values', function () { yAxis.yMax = 1; - let tickFormat = yAxis.getYAxis().tickFormat(); + const tickFormat = yAxis.getYAxis().tickFormat(); expect(tickFormat(0.8)).to.be('0.8'); }); @@ -384,23 +384,23 @@ describe('Vislib yAxis Class Test Suite', function () { }); describe('#tickFormat()', function () { - let formatter = function () {}; + const formatter = function () {}; it('returns a basic number formatter by default', function () { - let yAxis = buildYAxis(); + const yAxis = buildYAxis(); expect(yAxis.tickFormat()).to.not.be(formatter); expect(yAxis.tickFormat()(1)).to.be('1'); }); it('returns the yAxisFormatter when passed', function () { - let yAxis = buildYAxis({ + const yAxis = buildYAxis({ yAxisFormatter: formatter }); expect(yAxis.tickFormat()).to.be(formatter); }); it('returns a percentage formatter when the vis is in percentage mode', function () { - let yAxis = buildYAxis({ + const yAxis = buildYAxis({ yAxisFormatter: formatter, _attr: { mode: 'percentage' diff --git a/src/ui/public/vislib/__tests__/vis.js b/src/ui/public/vislib/__tests__/vis.js index 9129ca2a9d84c..f37a7100ce2de 100644 --- a/src/ui/public/vislib/__tests__/vis.js +++ b/src/ui/public/vislib/__tests__/vis.js @@ -1,5 +1,4 @@ import _ from 'lodash'; -import d3 from 'd3'; import expect from 'expect.js'; import ngMock from 'ng_mock'; @@ -11,14 +10,14 @@ import $ from 'jquery'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -let dataArray = [ +const dataArray = [ series, columns, rows, stackedSeries ]; -let names = [ +const names = [ 'series', 'columns', 'rows', @@ -28,8 +27,8 @@ let names = [ dataArray.forEach(function (data, i) { describe('Vislib Vis Test Suite for ' + names[i] + ' Data', function () { - let beforeEvent = 'click'; - let afterEvent = 'brush'; + const beforeEvent = 'click'; + const afterEvent = 'brush'; let vis; let persistedState; let secondVis; @@ -129,7 +128,7 @@ dataArray.forEach(function (data, i) { }); describe('on Method', function () { - let events = [ + const events = [ beforeEvent, afterEvent ]; @@ -173,7 +172,7 @@ dataArray.forEach(function (data, i) { }); it('should cause a listener for each event to be attached to each chart', function () { - let charts = vis.handler.charts; + const charts = vis.handler.charts; charts.forEach(function (chart, i) { expect(chart.events.listenerCount(beforeEvent)).to.be.above(0); @@ -220,7 +219,7 @@ dataArray.forEach(function (data, i) { }); it('should remove a listener', function () { - let charts = vis.handler.charts; + const charts = vis.handler.charts; expect(vis.listeners(beforeEvent)).to.not.contain(listener1); expect(vis.listeners(beforeEvent)).to.contain(listener2); @@ -236,7 +235,7 @@ dataArray.forEach(function (data, i) { }); it('should remove the event and all listeners when only event passed an argument', function () { - let charts = vis.handler.charts; + const charts = vis.handler.charts; vis.off(afterEvent); // should remove 'brush' event @@ -251,7 +250,7 @@ dataArray.forEach(function (data, i) { }); it('should remove the event from the chart when the last listener is removed', function () { - let charts = vis.handler.charts; + const charts = vis.handler.charts; vis.off(afterEvent, listener2); expect(vis.listenerCount(afterEvent)).to.be(0); diff --git a/src/ui/public/vislib/__tests__/visualizations/area_chart.js b/src/ui/public/vislib/__tests__/visualizations/area_chart.js index 72c125746e223..f59db623810f5 100644 --- a/src/ui/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/area_chart.js @@ -1,5 +1,4 @@ import d3 from 'd3'; -import angular from 'angular'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import _ from 'lodash'; @@ -9,7 +8,7 @@ import notQuiteEnoughVariables from 'fixtures/vislib/mock_data/not_enough_data/_ import $ from 'jquery'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -let someOtherVariables = { +const someOtherVariables = { 'series pos': require('fixtures/vislib/mock_data/date_histogram/_series'), 'series pos neg': require('fixtures/vislib/mock_data/date_histogram/_series_pos_neg'), 'series neg': require('fixtures/vislib/mock_data/date_histogram/_series_neg'), @@ -18,7 +17,7 @@ let someOtherVariables = { 'stackedSeries': require('fixtures/vislib/mock_data/date_histogram/_stacked_series') }; -let visLibParams = { +const visLibParams = { type: 'area', addLegend: true, addTooltip: true @@ -158,12 +157,12 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should not draw circles where d.y === 0', function () { vis.handler.charts.forEach(function (chart) { - let series = chart.chartData.series; - let isZero = series.some(function (d) { + const series = chart.chartData.series; + const isZero = series.some(function (d) { return d.y === 0; }); - let circles = $.makeArray($(chart.chartEl).find('circle')); - let isNotDrawn = circles.some(function (d) { + const circles = $.makeArray($(chart.chartEl).find('circle')); + const isNotDrawn = circles.some(function (d) { return d.__data__.y === 0; }); @@ -183,7 +182,7 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; + const yAxis = chart.handler.yAxis; expect(yAxis.domain[0]).to.not.be(undefined); expect(yAxis.domain[1]).to.not.be(undefined); @@ -192,7 +191,7 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; + const yAxis = chart.handler.yAxis; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -224,8 +223,8 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; - let yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; + const yAxis = chart.handler.yAxis; + const yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; expect(yAxis.domain[0]).to.equal(yVals[0]); expect(yAxis.domain[1]).to.equal(yVals[1]); diff --git a/src/ui/public/vislib/__tests__/visualizations/chart.js b/src/ui/public/vislib/__tests__/visualizations/chart.js index 44f178d660ee9..154fabfd8f290 100644 --- a/src/ui/public/vislib/__tests__/visualizations/chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/chart.js @@ -13,12 +13,12 @@ describe('Vislib _chart Test Suite', function () { let Data; let persistedState; let Vis; - let chartData = {}; + const chartData = {}; let vis; let el; let myChart; let config; - let data = { + const data = { hits : 621, label : '', ordered : { diff --git a/src/ui/public/vislib/__tests__/visualizations/column_chart.js b/src/ui/public/vislib/__tests__/visualizations/column_chart.js index e168d039513b5..3a5288e0d0990 100644 --- a/src/ui/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/column_chart.js @@ -1,5 +1,3 @@ - -import angular from 'angular'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import _ from 'lodash'; @@ -10,14 +8,14 @@ import series from 'fixtures/vislib/mock_data/date_histogram/_series'; import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; import termsColumns from 'fixtures/vislib/mock_data/terms/_columns'; -//let histogramRows = require('fixtures/vislib/mock_data/histogram/_rows'); +//const histogramRows = require('fixtures/vislib/mock_data/histogram/_rows'); import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; // tuple, with the format [description, mode, data] -let dataTypesArray = [ +const dataTypesArray = [ ['series', 'stacked', series], ['series with positive and negative values', 'stacked', seriesPosNeg], ['series with negative values', 'stacked', seriesNeg], @@ -27,14 +25,14 @@ let dataTypesArray = [ ]; dataTypesArray.forEach(function (dataType, i) { - let name = dataType[0]; - let mode = dataType[1]; - let data = dataType[2]; + const name = dataType[0]; + const mode = dataType[1]; + const data = dataType[2]; describe('Vislib Column Chart Test Suite for ' + name + ' Data', function () { let vis; let persistedState; - let visLibParams = { + const visLibParams = { type: 'histogram', hasTimeField: true, addLegend: true, @@ -104,7 +102,7 @@ dataTypesArray.forEach(function (dataType, i) { describe('addBarEvents method', function () { function checkChart(chart) { - let rect = $(chart.chartEl).find('.series rect').get(0); + const rect = $(chart.chartEl).find('.series rect').get(0); // check for existance of stuff and things return { @@ -121,23 +119,23 @@ dataTypesArray.forEach(function (dataType, i) { it('should attach the brush if data is a set of ordered dates', function () { vis.handler.charts.forEach(function (chart) { - let has = checkChart(chart); - let ordered = vis.handler.data.get('ordered'); - let date = Boolean(ordered && ordered.date); + const has = checkChart(chart); + const ordered = vis.handler.data.get('ordered'); + const date = Boolean(ordered && ordered.date); expect(has.brush).to.be(date); }); }); it('should attach a click event', function () { vis.handler.charts.forEach(function (chart) { - let has = checkChart(chart); + const has = checkChart(chart); expect(has.click).to.be(true); }); }); it('should attach a hover event', function () { vis.handler.charts.forEach(function (chart) { - let has = checkChart(chart); + const has = checkChart(chart); expect(has.mouseOver).to.be(true); }); }); @@ -152,7 +150,7 @@ dataTypesArray.forEach(function (dataType, i) { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; + const yAxis = chart.handler.yAxis; expect(yAxis.domain[0]).to.not.be(undefined); expect(yAxis.domain[1]).to.not.be(undefined); @@ -161,7 +159,7 @@ dataTypesArray.forEach(function (dataType, i) { it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; + const yAxis = chart.handler.yAxis; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -193,9 +191,9 @@ dataTypesArray.forEach(function (dataType, i) { it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; - let min = vis.handler.data.getYMin(); - let max = vis.handler.data.getYMax(); + const yAxis = chart.handler.yAxis; + const min = vis.handler.data.getYMin(); + const max = vis.handler.data.getYMax(); expect(yAxis.domain[0]).to.equal(min); expect(yAxis.domain[1]).to.equal(max); diff --git a/src/ui/public/vislib/__tests__/visualizations/line_chart.js b/src/ui/public/vislib/__tests__/visualizations/line_chart.js index bd4ffcb7b5885..0c4b02c99ace4 100644 --- a/src/ui/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/line_chart.js @@ -15,7 +15,7 @@ import $ from 'jquery'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -let dataTypes = [ +const dataTypes = [ ['series pos', seriesPos], ['series pos neg', seriesPosNeg], ['series neg', seriesNeg], @@ -26,8 +26,8 @@ let dataTypes = [ describe('Vislib Line Chart', function () { dataTypes.forEach(function (type, i) { - let name = type[0]; - let data = type[1]; + const name = type[0]; + const data = type[1]; describe(name + ' Data', function () { let vis; @@ -35,7 +35,7 @@ describe('Vislib Line Chart', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { - let visLibParams = { + const visLibParams = { type: 'line', addLegend: true, addTooltip: true, @@ -133,7 +133,7 @@ describe('Vislib Line Chart', function () { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; + const yAxis = chart.handler.yAxis; expect(yAxis.domain[0]).to.not.be(undefined); expect(yAxis.domain[1]).to.not.be(undefined); @@ -142,7 +142,7 @@ describe('Vislib Line Chart', function () { it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; + const yAxis = chart.handler.yAxis; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -174,8 +174,8 @@ describe('Vislib Line Chart', function () { it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - let yAxis = chart.handler.yAxis; - let yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; + const yAxis = chart.handler.yAxis; + const yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; expect(yAxis.domain[0]).to.equal(yVals[0]); expect(yAxis.domain[1]).to.equal(yVals[1]); diff --git a/src/ui/public/vislib/__tests__/visualizations/pie_chart.js b/src/ui/public/vislib/__tests__/visualizations/pie_chart.js index 26edaecadc6fa..3cbd29a52991f 100644 --- a/src/ui/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/pie_chart.js @@ -11,39 +11,39 @@ import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_s import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import AggResponseHierarchicalBuildHierarchicalDataProvider from 'ui/agg_response/hierarchical/build_hierarchical_data'; -let rowAgg = [ +const rowAgg = [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'split', params: { field: 'extension', rows: true }}, { type: 'terms', schema: 'segment', params: { field: 'machine.os' }}, { type: 'terms', schema: 'segment', params: { field: 'geo.src' }} ]; -let colAgg = [ +const colAgg = [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'split', params: { field: 'extension', row: false }}, { type: 'terms', schema: 'segment', params: { field: 'machine.os' }}, { type: 'terms', schema: 'segment', params: { field: 'geo.src' }} ]; -let sliceAgg = [ +const sliceAgg = [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'segment', params: { field: 'machine.os' }}, { type: 'terms', schema: 'segment', params: { field: 'geo.src' }} ]; -let aggArray = [ +const aggArray = [ rowAgg, colAgg, sliceAgg ]; -let names = [ +const names = [ 'rows', 'columns', 'slices' ]; -let sizes = [ +const sizes = [ 0, 5, 15, @@ -53,13 +53,13 @@ let sizes = [ ]; describe('No global chart settings', function () { - let visLibParams1 = { + const visLibParams1 = { el: '
', type: 'pie', addLegend: true, addTooltip: true }; - let visLibParams2 = { + const visLibParams2 = { el: '
', type: 'pie', addLegend: true, @@ -85,11 +85,11 @@ describe('No global chart settings', function () { let id1 = 1; let id2 = 1; - let stubVis1 = new Vis(indexPattern, { + const stubVis1 = new Vis(indexPattern, { type: 'pie', aggs: rowAgg }); - let stubVis2 = new Vis(indexPattern, { + const stubVis2 = new Vis(indexPattern, { type: 'pie', aggs: colAgg }); @@ -120,19 +120,19 @@ describe('No global chart settings', function () { }); describe('_validatePieData method', function () { - let allZeros = [ + const allZeros = [ { slices: { children: [] } }, { slices: { children: [] } }, { slices: { children: [] } } ]; - let someZeros = [ + const someZeros = [ { slices: { children: [{}] } }, { slices: { children: [{}] } }, { slices: { children: [] } } ]; - let noZeros = [ + const noZeros = [ { slices: { children: [{}] } }, { slices: { children: [{}] } }, { slices: { children: [{}] } } @@ -157,7 +157,7 @@ describe('No global chart settings', function () { aggArray.forEach(function (dataAgg, i) { describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () { - let visLibParams = { + const visLibParams = { type: 'pie', addLegend: true, addTooltip: true @@ -178,7 +178,7 @@ aggArray.forEach(function (dataAgg, i) { buildHierarchicalData = Private(AggResponseHierarchicalBuildHierarchicalDataProvider); let id = 1; - let stubVis = new Vis(indexPattern, { + const stubVis = new Vis(indexPattern, { type: 'pie', aggs: dataAgg }); diff --git a/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js b/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js index b74f779c1510b..3366a4a21200e 100644 --- a/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js +++ b/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js @@ -10,14 +10,14 @@ import $ from 'jquery'; import VislibVisualizationsMapProvider from 'ui/vislib/visualizations/_map'; // // Data -// let dataArray = [ +// const dataArray = [ // ['geojson', require('fixtures/vislib/mock_data/geohash/_geo_json')], // ['columns', require('fixtures/vislib/mock_data/geohash/_columns')], // ['rows', require('fixtures/vislib/mock_data/geohash/_rows')], // ]; // // TODO: Test the specific behavior of each these -// let mapTypes = [ +// const mapTypes = [ // 'Scaled Circle Markers', // 'Shaded Circle Markers', // 'Shaded Geohash Grid', @@ -25,10 +25,10 @@ import VislibVisualizationsMapProvider from 'ui/vislib/visualizations/_map'; // ]; describe('TileMap Map Tests', function () { - let $mockMapEl = $('
'); + const $mockMapEl = $('
'); let TileMapMap; - let leafletStubs = {}; - let leafletMocks = {}; + const leafletStubs = {}; + const leafletMocks = {}; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { @@ -57,7 +57,7 @@ describe('TileMap Map Tests', function () { }); it('should add zoom controls', function () { - let mapOptions = createStub.firstCall.args[0]; + const mapOptions = createStub.firstCall.args[0]; expect(mapOptions).to.be.an('object'); if (mapOptions.zoomControl) expect(mapOptions.zoomControl).to.be.ok(); @@ -82,8 +82,8 @@ describe('TileMap Map Tests', function () { expect(leafletStubs.tileLayer.callCount).to.equal(1); expect(leafletStubs.map.callCount).to.equal(1); - let callArgs = leafletStubs.map.firstCall.args; - let mapOptions = callArgs[1]; + const callArgs = leafletStubs.map.firstCall.args; + const mapOptions = callArgs[1]; expect(callArgs[0]).to.be($mockMapEl.get(0)); expect(mapOptions).to.have.property('zoom'); expect(mapOptions).to.have.property('center'); @@ -122,21 +122,21 @@ describe('TileMap Map Tests', function () { }); it('should attach interaction events', function () { - let expectedTileEvents = ['tileload']; - let expectedMapEvents = ['draw:created', 'moveend', 'zoomend', 'unload']; - let matchedEvents = { + const expectedTileEvents = ['tileload']; + const expectedMapEvents = ['draw:created', 'moveend', 'zoomend', 'unload']; + const matchedEvents = { tiles: 0, maps: 0, }; _.times(leafletMocks.tileLayer.on.callCount, function (index) { - let ev = leafletMocks.tileLayer.on.getCall(index).args[0]; + const ev = leafletMocks.tileLayer.on.getCall(index).args[0]; if (_.includes(expectedTileEvents, ev)) matchedEvents.tiles++; }); expect(matchedEvents.tiles).to.equal(expectedTileEvents.length); _.times(leafletMocks.map.on.callCount, function (index) { - let ev = leafletMocks.map.on.getCall(index).args[0]; + const ev = leafletMocks.map.on.getCall(index).args[0]; if (_.includes(expectedMapEvents, ev)) matchedEvents.maps++; }); expect(matchedEvents.maps).to.equal(expectedMapEvents.length); @@ -157,14 +157,14 @@ describe('TileMap Map Tests', function () { it('should pass the map options to the marker', function () { map._addMarkers(); - let args = createStub.firstCall.args[0]; + const args = createStub.firstCall.args[0]; expect(args).to.have.property('tooltipFormatter'); expect(args).to.have.property('valueFormatter'); expect(args).to.have.property('attr'); }); it('should destroy existing markers', function () { - let destroyStub = sinon.stub(); + const destroyStub = sinon.stub(); map._markers = { destroy: destroyStub }; map._addMarkers(); @@ -182,20 +182,20 @@ describe('TileMap Map Tests', function () { it('should return an empty array if no data', function () { map = new TileMapMap($mockMapEl, {}, {}); - let rects = map._getDataRectangles(); + const rects = map._getDataRectangles(); expect(rects).to.have.length(0); }); it('should return an array of arrays of rectangles', function () { - let rects = map._getDataRectangles(); + const rects = map._getDataRectangles(); _.times(5, function () { - let index = _.random(rects.length - 1); - let rect = rects[index]; - let featureRect = geoJsonData.geoJson.features[index].properties.rectangle; + const index = _.random(rects.length - 1); + const rect = rects[index]; + const featureRect = geoJsonData.geoJson.features[index].properties.rectangle; expect(rect.length).to.equal(featureRect.length); // should swap the array - let checkIndex = _.random(rect.length - 1); + const checkIndex = _.random(rect.length - 1); expect(rect[checkIndex]).to.eql(featureRect[checkIndex]); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/tile_maps/markers.js b/src/ui/public/vislib/__tests__/visualizations/tile_maps/markers.js index 04ed6fdbea14d..8d06738695399 100644 --- a/src/ui/public/vislib/__tests__/visualizations/tile_maps/markers.js +++ b/src/ui/public/vislib/__tests__/visualizations/tile_maps/markers.js @@ -12,9 +12,9 @@ import VislibVisualizationsMarkerTypesShadedCirclesProvider from 'ui/vislib/visu import VislibVisualizationsMarkerTypesScaledCirclesProvider from 'ui/vislib/visualizations/marker_types/scaled_circles'; import VislibVisualizationsMarkerTypesHeatmapProvider from 'ui/vislib/visualizations/marker_types/heatmap'; // defaults to roughly the lower 48 US states -let defaultSWCoords = [13.496, -143.789]; -let defaultNECoords = [55.526, -57.919]; -let bounds = {}; +const defaultSWCoords = [13.496, -143.789]; +const defaultNECoords = [55.526, -57.919]; +const bounds = {}; let MarkerType; let map; @@ -29,7 +29,7 @@ function getBounds() { return L.latLngBounds(bounds.southWest, bounds.northEast); } -let mockMap = { +const mockMap = { addLayer: _.noop, closePopup: _.noop, getBounds: getBounds, @@ -76,8 +76,8 @@ describe('Marker Tests', function () { it('should not filter any features', function () { // set bounds to the entire world setBounds([-87.252, -343.828], [87.252, 343.125]); - let boundFilter = markerLayer._filterToMapBounds(); - let mapFeature = mapData.features.filter(boundFilter); + const boundFilter = markerLayer._filterToMapBounds(); + const mapFeature = mapData.features.filter(boundFilter); expect(mapFeature.length).to.equal(mapData.features.length); }); @@ -85,8 +85,8 @@ describe('Marker Tests', function () { it('should filter out data points that are outside of the map bounds', function () { // set bounds to roughly US southwest setBounds([31.690, -124.387], [42.324, -102.919]); - let boundFilter = markerLayer._filterToMapBounds(); - let mapFeature = mapData.features.filter(boundFilter); + const boundFilter = markerLayer._filterToMapBounds(); + const mapFeature = mapData.features.filter(boundFilter); expect(mapFeature.length).to.be.lessThan(mapData.features.length); }); @@ -94,8 +94,8 @@ describe('Marker Tests', function () { describe('legendQuantizer', function () { it('should return a range of hex colors', function () { - let minColor = markerLayer._legendQuantizer(mapData.properties.allmin); - let maxColor = markerLayer._legendQuantizer(mapData.properties.allmax); + const minColor = markerLayer._legendQuantizer(mapData.properties.allmin); + const maxColor = markerLayer._legendQuantizer(mapData.properties.allmax); expect(minColor.substring(0, 1)).to.equal('#'); expect(minColor).to.have.length(7); @@ -105,18 +105,18 @@ describe('Marker Tests', function () { }); it('should return a color with 1 color', function () { - let geoJson = { properties: { min: 1, max: 1 } }; + const geoJson = { properties: { min: 1, max: 1 } }; markerLayer = createMarker(MarkerClass, geoJson); // ensure the quantizer domain is correct - let color = markerLayer._legendQuantizer(1); + const color = markerLayer._legendQuantizer(1); expect(color).to.not.be(undefined); expect(color.substring(0, 1)).to.equal('#'); // should always get the same color back _.times(5, function () { - let num = _.random(0, 100); - let randColor = markerLayer._legendQuantizer(0); + const num = _.random(0, 100); + const randColor = markerLayer._legendQuantizer(0); expect(randColor).to.equal(color); }); }); @@ -124,19 +124,19 @@ describe('Marker Tests', function () { describe('applyShadingStyle', function () { it('should return a style object', function () { - let style = markerLayer.applyShadingStyle(100); + const style = markerLayer.applyShadingStyle(100); expect(style).to.be.an('object'); - let keys = _.keys(style); - let expected = ['fillColor', 'color']; + const keys = _.keys(style); + const expected = ['fillColor', 'color']; _.each(expected, function (key) { expect(keys).to.contain(key); }); }); it('should use the legendQuantizer', function () { - let spy = sinon.spy(markerLayer, '_legendQuantizer'); - let style = markerLayer.applyShadingStyle(100); + const spy = sinon.spy(markerLayer, '_legendQuantizer'); + const style = markerLayer.applyShadingStyle(100); expect(spy.callCount).to.equal(1); }); }); @@ -144,9 +144,9 @@ describe('Marker Tests', function () { describe('showTooltip', function () { it('should use the tooltip formatter', function () { let content; - let sample = _.sample(mapData.features); + const sample = _.sample(mapData.features); - let stub = sinon.stub(markerLayer, '_tooltipFormatter', function (val) { + const stub = sinon.stub(markerLayer, '_tooltipFormatter', function (val) { return; }); @@ -186,12 +186,12 @@ describe('Marker Tests', function () { }); it('should use the value formatter', function () { - let formatterSpy = sinon.spy(markerLayer, '_valueFormatter'); + const formatterSpy = sinon.spy(markerLayer, '_valueFormatter'); // called twice for every legend color defined - let expectedCallCount = markerLayer._legendColors.length * 2; + const expectedCallCount = markerLayer._legendColors.length * 2; markerLayer.addLegend(); - let legend = markerLayer._legend.onAdd(); + const legend = markerLayer._legend.onAdd(); expect(formatterSpy.callCount).to.equal(expectedCallCount); expect(legend).to.be.a(HTMLDivElement); @@ -202,14 +202,14 @@ describe('Marker Tests', function () { describe('Shaded Circles', function () { beforeEach(ngMock.module('MarkerFactory')); beforeEach(ngMock.inject(function (Private) { - let MarkerClass = Private(VislibVisualizationsMarkerTypesShadedCirclesProvider); + const MarkerClass = Private(VislibVisualizationsMarkerTypesShadedCirclesProvider); markerLayer = createMarker(MarkerClass); })); describe('geohashMinDistance method', function () { it('should return a finite number', function () { - let sample = _.sample(mapData.features); - let distance = markerLayer._geohashMinDistance(sample); + const sample = _.sample(mapData.features); + const distance = markerLayer._geohashMinDistance(sample); expect(distance).to.be.a('number'); expect(_.isFinite(distance)).to.be(true); @@ -224,34 +224,34 @@ describe('Marker Tests', function () { beforeEach(ngMock.inject(function (Private) { zoom = _.random(1, 18); sinon.stub(mockMap, 'getZoom', _.constant(zoom)); - let MarkerClass = Private(VislibVisualizationsMarkerTypesScaledCirclesProvider); + const MarkerClass = Private(VislibVisualizationsMarkerTypesScaledCirclesProvider); markerLayer = createMarker(MarkerClass); })); describe('radiusScale method', function () { - let valueArray = [10, 20, 30, 40, 50, 60]; - let max = _.max(valueArray); - let prev = -1; + const valueArray = [10, 20, 30, 40, 50, 60]; + const max = _.max(valueArray); + const prev = -1; it('should return 0 for value of 0', function () { expect(markerLayer._radiusScale(0)).to.equal(0); }); it('should return a scaled value for negative and positive numbers', function () { - let upperBound = markerLayer._radiusScale(max); - let results = []; + const upperBound = markerLayer._radiusScale(max); + const results = []; function roundValue(value) { // round number to 6 decimal places - let r = Math.pow(10, 6); + const r = Math.pow(10, 6); return Math.round(value * r) / r; } _.each(valueArray, function (value, i) { - let ratio = Math.pow(value / max, 0.5); - let comparison = ratio * upperBound; - let radius = markerLayer._radiusScale(value); - let negRadius = markerLayer._radiusScale(value * -1); + const ratio = Math.pow(value / max, 0.5); + const comparison = ratio * upperBound; + const radius = markerLayer._radiusScale(value); + const negRadius = markerLayer._radiusScale(value * -1); results.push(radius); expect(negRadius).to.equal(radius); @@ -269,7 +269,7 @@ describe('Marker Tests', function () { describe('Heatmaps', function () { beforeEach(ngMock.module('MarkerFactory')); beforeEach(ngMock.inject(function (Private) { - let MarkerClass = Private(VislibVisualizationsMarkerTypesHeatmapProvider); + const MarkerClass = Private(VislibVisualizationsMarkerTypesHeatmapProvider); markerLayer = createMarker(MarkerClass); })); @@ -281,7 +281,7 @@ describe('Marker Tests', function () { }); it('should return an array or values for each feature', function () { - let arr = markerLayer._dataToHeatArray(max); + const arr = markerLayer._dataToHeatArray(max); expect(arr).to.be.an('array'); expect(arr).to.have.length(mapData.features.length); @@ -289,11 +289,11 @@ describe('Marker Tests', function () { it('should return an array item with lat, lng, metric for each feature', function () { _.times(3, function () { - let arr = markerLayer._dataToHeatArray(max); - let index = _.random(mapData.features.length - 1); - let feature = mapData.features[index]; - let featureValue = feature.properties.value; - let featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); + const arr = markerLayer._dataToHeatArray(max); + const index = _.random(mapData.features.length - 1); + const feature = mapData.features[index]; + const featureValue = feature.properties.value; + const featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); expect(arr[index]).to.eql(featureArr); }); }); @@ -302,11 +302,11 @@ describe('Marker Tests', function () { _.times(5, function () { markerLayer._attr.heatNormalizeData = true; - let arr = markerLayer._dataToHeatArray(max); - let index = _.random(mapData.features.length - 1); - let feature = mapData.features[index]; - let featureValue = feature.properties.value / max; - let featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); + const arr = markerLayer._dataToHeatArray(max); + const index = _.random(mapData.features.length - 1); + const feature = mapData.features[index]; + const featureValue = feature.properties.value / max; + const featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); expect(arr[index]).to.eql(featureArr); }); }); @@ -315,18 +315,18 @@ describe('Marker Tests', function () { describe('tooltipProximity', function () { it('should return true if feature is close enough to event latlng', function () { _.times(5, function () { - let feature = _.sample(mapData.features); - let point = markerLayer._getLatLng(feature); - let arr = markerLayer._tooltipProximity(point, feature); + const feature = _.sample(mapData.features); + const point = markerLayer._getLatLng(feature); + const arr = markerLayer._tooltipProximity(point, feature); expect(arr).to.be(true); }); }); it('should return false if feature is not close enough to event latlng', function () { _.times(5, function () { - let feature = _.sample(mapData.features); - let point = L.latLng(90, -180); - let arr = markerLayer._tooltipProximity(point, feature); + const feature = _.sample(mapData.features); + const point = L.latLng(90, -180); + const arr = markerLayer._tooltipProximity(point, feature); expect(arr).to.be(false); }); }); @@ -335,9 +335,9 @@ describe('Marker Tests', function () { describe('nearestFeature', function () { it('should return nearest geoJson feature object', function () { _.times(5, function () { - let feature = _.sample(mapData.features); - let point = markerLayer._getLatLng(feature); - let nearestPoint = markerLayer._nearestFeature(point); + const feature = _.sample(mapData.features); + const point = markerLayer._getLatLng(feature); + const nearestPoint = markerLayer._nearestFeature(point); expect(nearestPoint).to.equal(feature); }); }); @@ -345,15 +345,15 @@ describe('Marker Tests', function () { describe('getLatLng', function () { it('should return a leaflet latLng object', function () { - let feature = _.sample(mapData.features); - let latLng = markerLayer._getLatLng(feature); - let compare = L.latLng(feature.geometry.coordinates.slice(0).reverse()); + const feature = _.sample(mapData.features); + const latLng = markerLayer._getLatLng(feature); + const compare = L.latLng(feature.geometry.coordinates.slice(0).reverse()); expect(latLng).to.eql(compare); }); it('should memoize the result', function () { - let spy = sinon.spy(L, 'latLng'); - let feature = _.sample(mapData.features); + const spy = sinon.spy(L, 'latLng'); + const feature = _.sample(mapData.features); markerLayer._getLatLng(feature); expect(spy.callCount).to.be(1); diff --git a/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js b/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js index 411b924265c48..0803ad328bebd 100644 --- a/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js +++ b/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js @@ -8,7 +8,7 @@ import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json'; import MockMap from 'fixtures/tilemap_map'; import $ from 'jquery'; import VislibVisualizationsTileMapProvider from 'ui/vislib/visualizations/tile_map'; -let mockChartEl = $('
'); +const mockChartEl = $('
'); let TileMap; let extentsStub; @@ -18,7 +18,7 @@ function createTileMap(handler, chartEl, chartData) { chartEl = chartEl || mockChartEl; chartData = chartData || geoJsonData; - let tilemap = new TileMap(handler, chartEl, chartData); + const tilemap = new TileMap(handler, chartEl, chartData); return tilemap; } @@ -52,7 +52,7 @@ describe('TileMap Tests', function () { }); it('should call destroy for clean state', function () { - let destroySpy = sinon.spy(tilemap, 'destroy'); + const destroySpy = sinon.spy(tilemap, 'destroy'); tilemap.draw(); expect(destroySpy.callCount).to.equal(1); }); @@ -73,14 +73,14 @@ describe('TileMap Tests', function () { it('should append maps and required controls', function () { expect(tilemap.maps).to.have.length(1); - let map = tilemap.maps[0]; + const map = tilemap.maps[0]; expect(map.addTitle.callCount).to.equal(0); expect(map.addFitControl.callCount).to.equal(1); expect(map.addBoundingControl.callCount).to.equal(1); }); it('should only add controls if data exists', function () { - let noData = { + const noData = { geohashGridAgg: { vis: { params: {} } }, geoJson: { features: [], @@ -93,17 +93,17 @@ describe('TileMap Tests', function () { tilemap._appendMap($selection); expect(tilemap.maps).to.have.length(1); - let map = tilemap.maps[0]; + const map = tilemap.maps[0]; expect(map.addTitle.callCount).to.equal(0); expect(map.addFitControl.callCount).to.equal(0); expect(map.addBoundingControl.callCount).to.equal(0); }); it('should append title if set in the data object', function () { - let mapTitle = 'Test Title'; + const mapTitle = 'Test Title'; tilemap = createTileMap(null, null, _.assign({ title: mapTitle }, geoJsonData)); tilemap._appendMap($selection); - let map = tilemap.maps[0]; + const map = tilemap.maps[0]; expect(map.addTitle.callCount).to.equal(1); expect(map.addTitle.firstCall.calledWith(mapTitle)).to.equal(true); @@ -111,8 +111,8 @@ describe('TileMap Tests', function () { }); describe('destroy', function () { - let maps = []; - let mapCount = 5; + const maps = []; + const mapCount = 5; beforeEach(function () { _.times(mapCount, function () { diff --git a/src/ui/public/vislib/__tests__/visualizations/time_marker.js b/src/ui/public/vislib/__tests__/visualizations/time_marker.js index 4f13b37dfbadd..0f240cada0118 100644 --- a/src/ui/public/vislib/__tests__/visualizations/time_marker.js +++ b/src/ui/public/vislib/__tests__/visualizations/time_marker.js @@ -10,13 +10,13 @@ import $ from 'jquery'; import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; describe('Vislib Time Marker Test Suite', function () { - let height = 50; - let color = '#ff0000'; - let opacity = 0.5; - let width = 3; - let customClass = 'custom-time-marker'; - let dateMathTimes = ['now-1m', 'now-5m', 'now-15m']; - let myTimes = dateMathTimes.map(function (dateMathString) { + const height = 50; + const color = '#ff0000'; + const opacity = 0.5; + const width = 3; + const customClass = 'custom-time-marker'; + const dateMathTimes = ['now-1m', 'now-5m', 'now-15m']; + const myTimes = dateMathTimes.map(function (dateMathString) { return { time: dateMathString, class: customClass, @@ -25,14 +25,14 @@ describe('Vislib Time Marker Test Suite', function () { width: width }; }); - let getExtent = function (dataArray, func) { + const getExtent = function (dataArray, func) { return func(dataArray, function (obj) { return func(obj.values, function (d) { return d.x; }); }); }; - let times = []; + const times = []; let TimeMarker; let defaultMarker; let customMarker; diff --git a/src/ui/public/vislib/components/color/color.js b/src/ui/public/vislib/components/color/color.js index 24782c3b9ca20..3a7d4543fb7f9 100644 --- a/src/ui/public/vislib/components/color/color.js +++ b/src/ui/public/vislib/components/color/color.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import VislibComponentsColorMappedColorsProvider from 'ui/vislib/components/color/mapped_colors'; export default function ColorUtilService(Private) { - let mappedColors = Private(VislibComponentsColorMappedColorsProvider); + const mappedColors = Private(VislibComponentsColorMappedColorsProvider); /* * Accepts an array of strings or numbers that are used to create a diff --git a/src/ui/public/vislib/components/color/color_palette.js b/src/ui/public/vislib/components/color/color_palette.js index c380224e562f3..5b4586a2642d8 100644 --- a/src/ui/public/vislib/components/color/color_palette.js +++ b/src/ui/public/vislib/components/color/color_palette.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import VislibComponentsColorSeedColorsProvider from 'ui/vislib/components/color/seed_colors'; export default function ColorPaletteUtilService(Private) { - let seedColors = Private(VislibComponentsColorSeedColorsProvider); + const seedColors = Private(VislibComponentsColorSeedColorsProvider); /* @@ -12,10 +12,10 @@ export default function ColorPaletteUtilService(Private) { * new colors are generated up to the value of the input number. */ - let offset = 300; // Hue offset to start at + const offset = 300; // Hue offset to start at - let fraction = function (goal) { - let walkTree = function (numerator, denominator, bytes) { + const fraction = function (goal) { + const walkTree = function (numerator, denominator, bytes) { if (bytes.length) { return walkTree( (numerator * 2) + (bytes.pop() ? 1 : -1), @@ -28,7 +28,7 @@ export default function ColorPaletteUtilService(Private) { } }; - let b = (goal + 2) + const b = (goal + 2) .toString(2) .split('') .map(function (num) { @@ -45,9 +45,9 @@ export default function ColorPaletteUtilService(Private) { throw new TypeError('ColorPaletteUtilService expects a number'); } - let colors = seedColors; + const colors = seedColors; - let seedLength = seedColors.length; + const seedLength = seedColors.length; _.times(num - seedLength, function (i) { colors.push(d3.hsl((fraction(i + seedLength + 1) * 360 + offset) % 360, 0.5, 0.5).toString()); diff --git a/src/ui/public/vislib/components/color/mapped_colors.js b/src/ui/public/vislib/components/color/mapped_colors.js index 748c6e1218dae..d665dd5cae5a0 100644 --- a/src/ui/public/vislib/components/color/mapped_colors.js +++ b/src/ui/public/vislib/components/color/mapped_colors.js @@ -70,4 +70,4 @@ define((require) => (Private, config, $rootScope) => { } return new MappedColors(); -}); \ No newline at end of file +}); diff --git a/src/ui/public/vislib/components/labels/data_array.js b/src/ui/public/vislib/components/labels/data_array.js index 1b830826cf604..1aae7dbe9816a 100644 --- a/src/ui/public/vislib/components/labels/data_array.js +++ b/src/ui/public/vislib/components/labels/data_array.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import VislibComponentsLabelsFlattenSeriesProvider from 'ui/vislib/components/labels/flatten_series'; export default function GetArrayUtilService(Private) { - let flattenSeries = Private(VislibComponentsLabelsFlattenSeriesProvider); + const flattenSeries = Private(VislibComponentsLabelsFlattenSeriesProvider); /* * Accepts a Kibana data object and returns an array of values objects. diff --git a/src/ui/public/vislib/components/labels/labels.js b/src/ui/public/vislib/components/labels/labels.js index 72dde34576ee4..dab5a5e1486a0 100644 --- a/src/ui/public/vislib/components/labels/labels.js +++ b/src/ui/public/vislib/components/labels/labels.js @@ -4,9 +4,9 @@ import VislibComponentsLabelsUniqLabelsProvider from 'ui/vislib/components/label import VislibComponentsLabelsPiePieLabelsProvider from 'ui/vislib/components/labels/pie/pie_labels'; export default function LabelUtilService(Private) { - let createArr = Private(VislibComponentsLabelsDataArrayProvider); - let getArrOfUniqLabels = Private(VislibComponentsLabelsUniqLabelsProvider); - let getPieLabels = Private(VislibComponentsLabelsPiePieLabelsProvider); + const createArr = Private(VislibComponentsLabelsDataArrayProvider); + const getArrOfUniqLabels = Private(VislibComponentsLabelsUniqLabelsProvider); + const getPieLabels = Private(VislibComponentsLabelsPiePieLabelsProvider); /* * Accepts a Kibana data object and returns an array of unique labels (strings). diff --git a/src/ui/public/vislib/components/labels/pie/get_pie_names.js b/src/ui/public/vislib/components/labels/pie/get_pie_names.js index 4d157daa7934b..22ac25a686fcf 100644 --- a/src/ui/public/vislib/components/labels/pie/get_pie_names.js +++ b/src/ui/public/vislib/components/labels/pie/get_pie_names.js @@ -2,10 +2,10 @@ import _ from 'lodash'; import VislibComponentsLabelsPieReturnPieNamesProvider from 'ui/vislib/components/labels/pie/return_pie_names'; export default function GetPieNames(Private) { - let returnNames = Private(VislibComponentsLabelsPieReturnPieNamesProvider); + const returnNames = Private(VislibComponentsLabelsPieReturnPieNamesProvider); return function (data, columns) { - let slices = data.slices; + const slices = data.slices; if (slices.children) { return _(returnNames(slices.children, 0, columns)) diff --git a/src/ui/public/vislib/components/labels/pie/pie_labels.js b/src/ui/public/vislib/components/labels/pie/pie_labels.js index a4741102e6228..250563c6586e5 100644 --- a/src/ui/public/vislib/components/labels/pie/pie_labels.js +++ b/src/ui/public/vislib/components/labels/pie/pie_labels.js @@ -3,17 +3,17 @@ import VislibComponentsLabelsPieRemoveZeroSlicesProvider from 'ui/vislib/compone import VislibComponentsLabelsPieGetPieNamesProvider from 'ui/vislib/components/labels/pie/get_pie_names'; export default function PieLabels(Private) { - let removeZeroSlices = Private(VislibComponentsLabelsPieRemoveZeroSlicesProvider); - let getNames = Private(VislibComponentsLabelsPieGetPieNamesProvider); + const removeZeroSlices = Private(VislibComponentsLabelsPieRemoveZeroSlicesProvider); + const getNames = Private(VislibComponentsLabelsPieGetPieNamesProvider); return function (obj) { if (!_.isObject(obj)) { throw new TypeError('PieLabel expects an object'); } - let data = obj.columns || obj.rows || [obj]; - let names = []; + const data = obj.columns || obj.rows || [obj]; + const names = []; data.forEach(function (obj) { - let columns = obj.raw ? obj.raw.columns : undefined; + const columns = obj.raw ? obj.raw.columns : undefined; obj.slices = removeZeroSlices(obj.slices); getNames(obj, columns).forEach(function (name) { diff --git a/src/ui/public/vislib/components/labels/pie/return_pie_names.js b/src/ui/public/vislib/components/labels/pie/return_pie_names.js index e62f71499222b..bb69a9542c870 100644 --- a/src/ui/public/vislib/components/labels/pie/return_pie_names.js +++ b/src/ui/public/vislib/components/labels/pie/return_pie_names.js @@ -1,7 +1,7 @@ define(function () { return function ReturnPieNames() { return function returnNames(array, index, columns) { - let names = []; + const names = []; array.forEach(function (obj) { names.push({ key: obj.name, index: index }); diff --git a/src/ui/public/vislib/components/tooltip/__tests__/positioning.js b/src/ui/public/vislib/components/tooltip/__tests__/positioning.js index 9edb8b45415d8..828d6c739cf93 100644 --- a/src/ui/public/vislib/components/tooltip/__tests__/positioning.js +++ b/src/ui/public/vislib/components/tooltip/__tests__/positioning.js @@ -6,17 +6,17 @@ import posTT from '../position_tooltip'; describe('Tooltip Positioning', function () { - let positions = ['north', 'south', 'east', 'west']; - let bounds = ['top', 'left', 'bottom', 'right']; + const positions = ['north', 'south', 'east', 'west']; + const bounds = ['top', 'left', 'bottom', 'right']; let $window; let $chart; let $tooltip; let $sizer; function testEl(width, height, $children) { - let $el = $('
'); + const $el = $('
'); - let size = { + const size = { width: _.random(width[0], width[1]), height: _.random(height[0], height[1]) }; @@ -57,7 +57,7 @@ describe('Tooltip Positioning', function () { xPercent = xPercent || 0.5; yPercent = yPercent || 0.5; - let base = $chart.offset(); + const base = $chart.offset(); return { clientX: base.left + ($chart.testSize.width * xPercent), @@ -67,14 +67,14 @@ describe('Tooltip Positioning', function () { describe('getTtSize()', function () { it('should measure the outer-size of the tooltip using an un-obstructed clone', function () { - let w = sinon.spy($.fn, 'outerWidth'); - let h = sinon.spy($.fn, 'outerHeight'); + const w = sinon.spy($.fn, 'outerWidth'); + const h = sinon.spy($.fn, 'outerHeight'); posTT.getTtSize($tooltip.html(), $sizer); [w, h].forEach(function (spy) { expect(spy).to.have.property('callCount', 1); - let matchHtml = w.thisValues.filter(function ($t) { + const matchHtml = w.thisValues.filter(function ($t) { return !$t.is($tooltip) && $t.html() === $tooltip.html(); }); expect(matchHtml).to.have.length(1); @@ -84,8 +84,8 @@ describe('Tooltip Positioning', function () { describe('getBasePosition()', function () { it('calculates the offset values for the four positions', function () { - let size = posTT.getTtSize($tooltip.html(), $sizer); - let pos = posTT.getBasePosition(size, makeEvent()); + const size = posTT.getTtSize($tooltip.html(), $sizer); + const pos = posTT.getBasePosition(size, makeEvent()); positions.forEach(function (p) { expect(pos).to.have.property(p); @@ -98,7 +98,7 @@ describe('Tooltip Positioning', function () { describe('getBounds()', function () { it('returns the offsets for the tlrb of the element', function () { - let cbounds = posTT.getBounds($chart); + const cbounds = posTT.getBounds($chart); bounds.forEach(function (b) { expect(cbounds).to.have.property(b); @@ -114,14 +114,14 @@ describe('Tooltip Positioning', function () { // size the tooltip very small so it won't collide with the edges $tooltip.css({ width: 15, height: 15 }); $sizer.css({ width: 15, height: 15 }); - let size = posTT.getTtSize($tooltip.html(), $sizer); + const size = posTT.getTtSize($tooltip.html(), $sizer); expect(size).to.have.property('width', 15); expect(size).to.have.property('height', 15); // position the element based on a mouse that is in the middle of the chart - let pos = posTT.getBasePosition(size, makeEvent(0.5, 0.5)); + const pos = posTT.getBasePosition(size, makeEvent(0.5, 0.5)); - let overflow = posTT.getOverflow(size, pos, [$chart, $window]); + const overflow = posTT.getOverflow(size, pos, [$chart, $window]); positions.forEach(function (p) { expect(overflow).to.have.property(p); @@ -131,11 +131,11 @@ describe('Tooltip Positioning', function () { }); it('identifies an overflow with a positive value in that direction', function () { - let size = posTT.getTtSize($tooltip.html(), $sizer); + const size = posTT.getTtSize($tooltip.html(), $sizer); // position the element based on a mouse that is in the bottom right hand courner of the chart - let pos = posTT.getBasePosition(size, makeEvent(0.99, 0.99)); - let overflow = posTT.getOverflow(size, pos, [$chart, $window]); + const pos = posTT.getBasePosition(size, makeEvent(0.99, 0.99)); + const overflow = posTT.getOverflow(size, pos, [$chart, $window]); positions.forEach(function (p) { expect(overflow).to.have.property(p); @@ -157,9 +157,9 @@ describe('Tooltip Positioning', function () { }); function check(xPercent, yPercent/*, prev, directions... */) { - let directions = _.drop(arguments, 2); - let event = makeEvent(xPercent, yPercent); - let placement = posTT({ + const directions = _.drop(arguments, 2); + const event = makeEvent(xPercent, yPercent); + const placement = posTT({ $window: $window, $chart: $chart, $sizer: $sizer, @@ -214,12 +214,12 @@ describe('Tooltip Positioning', function () { describe('maintain the direction of the tooltip on reposition', function () { it('mouse moves from the top right to the middle', function () { - let pos = check(0.99, 0.10, 'bottom', 'left'); + const pos = check(0.99, 0.10, 'bottom', 'left'); check(0.50, 0.50, pos, 'bottom', 'left'); }); it('mouse moves from the bottom left to the middle', function () { - let pos = check(0.10, 0.99, 'top', 'right'); + const pos = check(0.10, 0.99, 'top', 'right'); check(0.50, 0.50, pos, 'top', 'right'); }); }); diff --git a/src/ui/public/vislib/components/tooltip/position_tooltip.js b/src/ui/public/vislib/components/tooltip/position_tooltip.js index 14051f5c862c9..2230bf2dd771a 100644 --- a/src/ui/public/vislib/components/tooltip/position_tooltip.js +++ b/src/ui/public/vislib/components/tooltip/position_tooltip.js @@ -1,31 +1,31 @@ import _ from 'lodash'; import $ from 'jquery'; -let OFFSET = 10; +const OFFSET = 10; let $clone; // translate css properties into their basic direction -let propDirs = { +const propDirs = { top: 'north', left: 'west' }; function positionTooltip(opts, html) { if (!opts) return; - let $chart = $(opts.$chart); - let $el = $(opts.$el); - let $window = $(opts.$window || window); - let $sizer = $(opts.$sizer); - let prev = $chart.data('previousPlacement') || {}; - let event = opts.event; + const $chart = $(opts.$chart); + const $el = $(opts.$el); + const $window = $(opts.$window || window); + const $sizer = $(opts.$sizer); + const prev = $chart.data('previousPlacement') || {}; + const event = opts.event; if (!$chart.size() || !$el.size()) return; - let size = getTtSize(html || $el.html(), $sizer); - let pos = getBasePosition(size, event); - let overflow = getOverflow(size, pos, [$chart, $window]); + const size = getTtSize(html || $el.html(), $sizer); + const pos = getBasePosition(size, event); + const overflow = getOverflow(size, pos, [$chart, $window]); - let placement = placeToAvoidOverflow(pos, prev, overflow); + const placement = placeToAvoidOverflow(pos, prev, overflow); $chart.data('previousPlacement', placement); return placement; } @@ -35,7 +35,7 @@ function getTtSize(ttHtml, $sizer) { $sizer.html(ttHtml); } - let size = { + const size = { width: $sizer.outerWidth(), height: $sizer.outerHeight() }; @@ -55,7 +55,7 @@ function getBasePosition(size, event) { function getBounds($el) { // in testing, $window is not actually a window, so we need to add // the offsets to make it work right. - let bounds = $el.offset() || { top: 0, left: 0 }; + const bounds = $el.offset() || { top: 0, left: 0 }; bounds.top += $el.scrollTop(); bounds.left += $el.scrollLeft(); bounds.bottom = bounds.top + $el.outerHeight(); @@ -64,7 +64,7 @@ function getBounds($el) { } function getOverflow(size, pos, containers) { - let overflow = {}; + const overflow = {}; containers.map(getBounds).forEach(function (bounds) { // number of pixels that the toolip would overflow it's far @@ -90,10 +90,10 @@ function mergeOverflows(dest, src) { } function pickPlacement(prop, pos, overflow, prev, pref, fallback, placement) { - let stash = '_' + prop; + const stash = '_' + prop; // list of directions in order of preference - let dirs = _.unique([prev[stash], pref, fallback].filter(Boolean)); + const dirs = _.unique([prev[stash], pref, fallback].filter(Boolean)); let dir; let value; @@ -126,7 +126,7 @@ function pickPlacement(prop, pos, overflow, prev, pref, fallback, placement) { } function placeToAvoidOverflow(pos, prev, overflow) { - let placement = {}; + const placement = {}; pickPlacement('top', pos, overflow, prev, 'south', 'north', placement); pickPlacement('left', pos, overflow, prev, 'east', 'west', placement); return placement; diff --git a/src/ui/public/vislib/components/tooltip/tooltip.js b/src/ui/public/vislib/components/tooltip/tooltip.js index 451bc5528d4a8..0bcd2794c1803 100644 --- a/src/ui/public/vislib/components/tooltip/tooltip.js +++ b/src/ui/public/vislib/components/tooltip/tooltip.js @@ -59,13 +59,13 @@ Tooltip.prototype.$getSizer = _.once(function () { * Show the tooltip, positioning it based on the content and chart container */ Tooltip.prototype.show = function () { - let $tooltip = this.$get(); - let $chart = this.$getChart(); - let html = $tooltip.html(); + const $tooltip = this.$get(); + const $chart = this.$getChart(); + const html = $tooltip.html(); if (!$chart) return; - let placement = positionTooltip({ + const placement = positionTooltip({ $window: $(window), $chart: $chart, $el: $tooltip, @@ -84,7 +84,7 @@ Tooltip.prototype.show = function () { * Hide the tooltip, clearing its contents */ Tooltip.prototype.hide = function () { - let $tooltip = this.$get(); + const $tooltip = this.$get(); allContents = []; $tooltip.css({ visibility: 'hidden', @@ -100,7 +100,7 @@ Tooltip.prototype.hide = function () { * @return {Object} jQuery node for the chart */ Tooltip.prototype.$getChart = function () { - let chart = $(this.container && this.container.node()); + const chart = $(this.container && this.container.node()); return chart.size() ? chart : false; }; @@ -111,7 +111,7 @@ Tooltip.prototype.$getChart = function () { * @return {Function} Renders tooltip on a D3 selection */ Tooltip.prototype.render = function () { - let self = this; + const self = this; /** * Calculates values for the tooltip placement @@ -119,19 +119,17 @@ Tooltip.prototype.render = function () { * @param {Object} selection D3 selection object */ return function (selection) { - let $tooltip = self.$get(); - let id = self.id; - let order = self.order; - - let tooltipSelection = d3.select($tooltip.get(0)); + const $tooltip = self.$get(); + const id = self.id; + const order = self.order; if (self.container === undefined || self.container !== d3.select(self.el).select('.' + self.containerClass)) { self.container = d3.select(self.el).select('.' + self.containerClass); } - let $chart = self.$getChart(); + const $chart = self.$getChart(); if ($chart) { - self.binder.jqOn($chart, 'mouseleave', function (event) { + self.binder.jqOn($chart, 'mouseleave', function () { // only clear when we leave the chart, so that // moving between points doesn't make it reposition $chart.removeData('previousPlacement'); @@ -139,7 +137,7 @@ Tooltip.prototype.render = function () { } selection.each(function (d, i) { - let element = d3.select(this); + const element = d3.select(this); function render(html) { allContents = _.filter(allContents, function (content) { @@ -148,7 +146,7 @@ Tooltip.prototype.render = function () { if (html) allContents.push({ id: id, html: html, order: order }); - let allHtml = _(allContents) + const allHtml = _(allContents) .sortBy('order') .pluck('html') .compact() @@ -167,7 +165,7 @@ Tooltip.prototype.render = function () { return render(); } - let events = self.events ? self.events.eventResponse(d, i) : d; + const events = self.events ? self.events.eventResponse(d, i) : d; return render(self.formatter(events)); }); diff --git a/src/ui/public/vislib/components/zero_injection/inject_zeros.js b/src/ui/public/vislib/components/zero_injection/inject_zeros.js index f738a7e424867..44479674ac987 100644 --- a/src/ui/public/vislib/components/zero_injection/inject_zeros.js +++ b/src/ui/public/vislib/components/zero_injection/inject_zeros.js @@ -4,9 +4,9 @@ import VislibComponentsZeroInjectionZeroFilledArrayProvider from 'ui/vislib/comp import VislibComponentsZeroInjectionZeroFillDataArrayProvider from 'ui/vislib/components/zero_injection/zero_fill_data_array'; export default function ZeroInjectionUtilService(Private) { - let orderXValues = Private(VislibComponentsZeroInjectionOrderedXKeysProvider); - let createZeroFilledArray = Private(VislibComponentsZeroInjectionZeroFilledArrayProvider); - let zeroFillDataArray = Private(VislibComponentsZeroInjectionZeroFillDataArrayProvider); + const orderXValues = Private(VislibComponentsZeroInjectionOrderedXKeysProvider); + const createZeroFilledArray = Private(VislibComponentsZeroInjectionZeroFilledArrayProvider); + const zeroFillDataArray = Private(VislibComponentsZeroInjectionZeroFillDataArrayProvider); /* * A Kibana data object may have multiple series with different array lengths. @@ -34,12 +34,12 @@ export default function ZeroInjectionUtilService(Private) { throw new TypeError('ZeroInjectionUtilService expects an object with a series, rows, or columns key'); } - let keys = orderXValues(obj); - let arr = getDataArray(obj); + const keys = orderXValues(obj); + const arr = getDataArray(obj); arr.forEach(function (object) { object.series.forEach(function (series) { - let zeroArray = createZeroFilledArray(keys); + const zeroArray = createZeroFilledArray(keys); series.values = zeroFillDataArray(zeroArray, series.values); }); diff --git a/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js b/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js index 1deab31715ec1..13afcb736d59a 100644 --- a/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js +++ b/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import moment from 'moment'; import VislibComponentsZeroInjectionUniqKeysProvider from 'ui/vislib/components/zero_injection/uniq_keys'; export default function OrderedXKeysUtilService(Private) { - let getUniqKeys = Private(VislibComponentsZeroInjectionUniqKeysProvider); + const getUniqKeys = Private(VislibComponentsZeroInjectionUniqKeysProvider); /* * Accepts a Kibana data object and returns @@ -16,11 +16,11 @@ export default function OrderedXKeysUtilService(Private) { throw new Error('OrderedXKeysUtilService expects an object'); } - let uniqKeys = getUniqKeys(obj); - let uniqKeysPairs = [...uniqKeys.entries()]; + const uniqKeys = getUniqKeys(obj); + const uniqKeysPairs = [...uniqKeys.entries()]; - let interval = _.get(obj, 'ordered.interval'); - let dateInterval = moment.isDuration(interval) ? interval : false; + const interval = _.get(obj, 'ordered.interval'); + const dateInterval = moment.isDuration(interval) ? interval : false; return _(uniqKeysPairs) .sortBy(function (d) { @@ -32,13 +32,13 @@ export default function OrderedXKeysUtilService(Private) { .map(function (d, i, list) { if (!d[1].isNumber) return d[0]; - let val = +d[0]; + const val = +d[0]; if (interval == null) return val; - let gapEdge = parseFloat(_.get(list, [i + 1, 0])); + const gapEdge = parseFloat(_.get(list, [i + 1, 0])); if (isNaN(gapEdge)) return val; - let vals = []; + const vals = []; let next = val; if (dateInterval) { diff --git a/src/ui/public/vislib/components/zero_injection/uniq_keys.js b/src/ui/public/vislib/components/zero_injection/uniq_keys.js index 0afe4705b579f..144a59f16f737 100644 --- a/src/ui/public/vislib/components/zero_injection/uniq_keys.js +++ b/src/ui/public/vislib/components/zero_injection/uniq_keys.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import VislibComponentsZeroInjectionFlattenDataProvider from 'ui/vislib/components/zero_injection/flatten_data'; export default function UniqueXValuesUtilService(Private) { - let flattenDataArray = Private(VislibComponentsZeroInjectionFlattenDataProvider); + const flattenDataArray = Private(VislibComponentsZeroInjectionFlattenDataProvider); /* * Accepts a Kibana data object. @@ -16,8 +16,8 @@ export default function UniqueXValuesUtilService(Private) { throw new TypeError('UniqueXValuesUtilService expects an object'); } - let flattenedData = flattenDataArray(obj); - let uniqueXValues = new Map(); + const flattenedData = flattenDataArray(obj); + const uniqueXValues = new Map(); let charts; if (!obj.series) { @@ -26,17 +26,17 @@ export default function UniqueXValuesUtilService(Private) { charts = [obj]; } - let isDate = charts.every(function (chart) { + const isDate = charts.every(function (chart) { return chart.ordered && chart.ordered.date; }); - let isOrdered = charts.every(function (chart) { + const isOrdered = charts.every(function (chart) { return chart.ordered; }); flattenedData.forEach(function (d, i) { - let key = d.x; - let prev = uniqueXValues.get(key); + const key = d.x; + const prev = uniqueXValues.get(key); if (d.xi != null) { i = d.xi; diff --git a/src/ui/public/vislib/components/zero_injection/zero_fill_data_array.js b/src/ui/public/vislib/components/zero_injection/zero_fill_data_array.js index 1055e8000f6f8..e997c7d377a90 100644 --- a/src/ui/public/vislib/components/zero_injection/zero_fill_data_array.js +++ b/src/ui/public/vislib/components/zero_injection/zero_fill_data_array.js @@ -15,9 +15,9 @@ export default function ZeroFillDataArrayUtilService(Private) { let i; let val; let index; - let max = arr2.length; + const max = arr2.length; - let getX = function (d) { + const getX = function (d) { return d.x === val.x; }; diff --git a/src/ui/public/vislib/components/zero_injection/zero_filled_array.js b/src/ui/public/vislib/components/zero_injection/zero_filled_array.js index 3a78af8065d99..ceeb4afc0c2f1 100644 --- a/src/ui/public/vislib/components/zero_injection/zero_filled_array.js +++ b/src/ui/public/vislib/components/zero_injection/zero_filled_array.js @@ -12,7 +12,7 @@ define(function () { throw new Error('ZeroFilledArrayUtilService expects an array of strings or numbers'); } - let zeroFilledArray = []; + const zeroFilledArray = []; arr.forEach(function (val) { zeroFilledArray.push({ diff --git a/src/ui/public/vislib/lib/_error_handler.js b/src/ui/public/vislib/lib/_error_handler.js index 3baa5da76a11d..f7e67dbf0d21a 100644 --- a/src/ui/public/vislib/lib/_error_handler.js +++ b/src/ui/public/vislib/lib/_error_handler.js @@ -9,25 +9,29 @@ export default function ErrorHandlerFactory() { * @class ErrorHandler * @constructor */ - function ErrorHandler() {} + class ErrorHandler { + constructor() { - /** - * Validates the height and width are > 0 - * min size must be at least 1 px - * - * @method validateWidthandHeight - * @param width {Number} HTMLElement width - * @param height {Number} HTMLElement height - * @returns {HTMLElement} HTML div with an error message - */ - ErrorHandler.prototype.validateWidthandHeight = function (width, height) { - let badWidth = _.isNaN(width) || width <= 0; - let badHeight = _.isNaN(height) || height <= 0; - - if (badWidth || badHeight) { - throw new errors.ContainerTooSmall(); } - }; + + /** + * Validates the height and width are > 0 + * min size must be at least 1 px + * + * @method validateWidthandHeight + * @param width {Number} HTMLElement width + * @param height {Number} HTMLElement height + * @returns {HTMLElement} HTML div with an error message + */ + validateWidthandHeight(width, height) { + const badWidth = _.isNaN(width) || width <= 0; + const badHeight = _.isNaN(height) || height <= 0; + + if (badWidth || badHeight) { + throw new errors.ContainerTooSmall(); + } + }; + } return ErrorHandler; }; diff --git a/src/ui/public/vislib/lib/alerts.js b/src/ui/public/vislib/lib/alerts.js index 7f44f7a9608f7..35b22ef1e5cdd 100644 --- a/src/ui/public/vislib/lib/alerts.js +++ b/src/ui/public/vislib/lib/alerts.js @@ -1,4 +1,3 @@ -import d3 from 'd3'; import $ from 'jquery'; import _ from 'lodash'; import Binder from 'ui/binder'; @@ -11,90 +10,88 @@ export default function AlertsFactory(Private) { * @constructor * @param el {HTMLElement} Reference to DOM element */ - function Alerts(vis, data, alertDefs) { - if (!(this instanceof Alerts)) { - return new Alerts(vis, data, alertDefs); + class Alerts { + constructor(vis, data, alertDefs) { + this.vis = vis; + this.data = data; + this.binder = new Binder(); + this.alertDefs = alertDefs || []; + + this.binder.jqOn(vis.el, 'mouseenter', '.vis-alerts-tray', function () { + const $tray = $(this); + hide(); + $(vis.el).on('mousemove', checkForExit); + + function hide() { + $tray.css({ + 'pointer-events': 'none', + opacity: 0.3 + }); + } + + function show() { + $(vis.el).off('mousemove', checkForExit); + $tray.css({ + 'pointer-events': 'auto', + opacity: 1 + }); + } + + function checkForExit(event) { + const pos = $tray.offset(); + if (pos.top > event.clientY || pos.left > event.clientX) return show(); + + const bottom = pos.top + $tray.height(); + if (event.clientY > bottom) return show(); + + const right = pos.left + $tray.width(); + if (event.clientX > right) return show(); + } + }); } - this.vis = vis; - this.data = data; - this.binder = new Binder(); - this.alertDefs = alertDefs || []; - - this.binder.jqOn(vis.el, 'mouseenter', '.vis-alerts-tray', function () { - let $tray = $(this); - hide(); - $(vis.el).on('mousemove', checkForExit); - - function hide() { - $tray.css({ - 'pointer-events': 'none', - opacity: 0.3 - }); - } - - function show() { - $(vis.el).off('mousemove', checkForExit); - $tray.css({ - 'pointer-events': 'auto', - opacity: 1 - }); - } - - function checkForExit(event) { - let pos = $tray.offset(); - if (pos.top > event.clientY || pos.left > event.clientX) return show(); - - let bottom = pos.top + $tray.height(); - if (event.clientY > bottom) return show(); - - let right = pos.left + $tray.width(); - if (event.clientX > right) return show(); - } - }); + /** + * Renders chart titles + * + * @method render + * @returns {D3.Selection|D3.Transition.Transition} DOM element with chart titles + */ + render() { + const vis = this.vis; + const data = this.data; + + const alerts = _(this.alertDefs) + .map(function (alertDef) { + if (!alertDef) return; + if (alertDef.test && !alertDef.test(vis, data)) return; + + const type = alertDef.type || 'info'; + const icon = alertDef.icon || type; + const msg = alertDef.msg; + + // alert container + const $icon = $('').addClass('vis-alerts-icon fa fa-' + icon); + const $text = $('

').addClass('vis-alerts-text').text(msg); + + return $('

').addClass('vis-alert vis-alert-' + type).append([$icon, $text]); + }) + .compact(); + + if (!alerts.size()) return; + + $(vis.el).find('.vis-alerts').append( + $('
').addClass('vis-alerts-tray').append(alerts.value()) + ); + }; + + /** + * Tear down the Alerts + * @return {undefined} + */ + destroy() { + this.binder.destroy(); + }; } - /** - * Renders chart titles - * - * @method render - * @returns {D3.Selection|D3.Transition.Transition} DOM element with chart titles - */ - Alerts.prototype.render = function () { - let vis = this.vis; - let data = this.data; - - let alerts = _(this.alertDefs) - .map(function (alertDef) { - if (!alertDef) return; - if (alertDef.test && !alertDef.test(vis, data)) return; - - let type = alertDef.type || 'info'; - let icon = alertDef.icon || type; - let msg = alertDef.msg; - - // alert container - let $icon = $('').addClass('vis-alerts-icon fa fa-' + icon); - let $text = $('

').addClass('vis-alerts-text').text(msg); - - return $('

').addClass('vis-alert vis-alert-' + type).append([$icon, $text]); - }) - .compact(); - - if (!alerts.size()) return; - - $(vis.el).find('.vis-alerts').append( - $('
').addClass('vis-alerts-tray').append(alerts.value()) - ); - }; - - /** - * Tear down the Alerts - * @return {undefined} - */ - Alerts.prototype.destroy = function () { - this.binder.destroy(); - }; - return Alerts; }; diff --git a/src/ui/public/vislib/lib/axis_title.js b/src/ui/public/vislib/lib/axis_title.js index 297f55a9be81b..7fa2f54230d23 100644 --- a/src/ui/public/vislib/lib/axis_title.js +++ b/src/ui/public/vislib/lib/axis_title.js @@ -1,10 +1,9 @@ import d3 from 'd3'; import $ from 'jquery'; -import _ from 'lodash'; import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; export default function AxisTitleFactory(Private) { - let ErrorHandler = Private(VislibLibErrorHandlerProvider); + const ErrorHandler = Private(VislibLibErrorHandlerProvider); /** * Appends axis title(s) to the visualization @@ -15,62 +14,60 @@ export default function AxisTitleFactory(Private) { * @param xTitle {String} X-axis title * @param yTitle {String} Y-axis title */ - _.class(AxisTitle).inherits(ErrorHandler); - function AxisTitle(el, xTitle, yTitle) { - if (!(this instanceof AxisTitle)) { - return new AxisTitle(el, xTitle, yTitle); + class AxisTitle extends ErrorHandler { + constructor(el, xTitle, yTitle) { + super(); + this.el = el; + this.xTitle = xTitle; + this.yTitle = yTitle; } - this.el = el; - this.xTitle = xTitle; - this.yTitle = yTitle; - } - - /** - * Renders both x and y axis titles - * - * @method render - * @returns {HTMLElement} DOM Element with axis titles - */ - AxisTitle.prototype.render = function () { - d3.select(this.el).select('.x-axis-title').call(this.draw(this.xTitle)); - d3.select(this.el).select('.y-axis-title').call(this.draw(this.yTitle)); - }; + /** + * Renders both x and y axis titles + * + * @method render + * @returns {HTMLElement} DOM Element with axis titles + */ + render() { + d3.select(this.el).select('.x-axis-title').call(this.draw(this.xTitle)); + d3.select(this.el).select('.y-axis-title').call(this.draw(this.yTitle)); + }; - /** - * Appends an SVG with title text - * - * @method draw - * @param title {String} Axis title - * @returns {Function} Appends axis title to a D3 selection - */ - AxisTitle.prototype.draw = function (title) { - let self = this; + /** + * Appends an SVG with title text + * + * @method draw + * @param title {String} Axis title + * @returns {Function} Appends axis title to a D3 selection + */ + draw(title) { + const self = this; - return function (selection) { - selection.each(function () { - let el = this; - let div = d3.select(el); - let width = $(el).width(); - let height = $(el).height(); + return function (selection) { + selection.each(function () { + const el = this; + const div = d3.select(el); + const width = $(el).width(); + const height = $(el).height(); - self.validateWidthandHeight(width, height); + self.validateWidthandHeight(width, height); - div.append('svg') - .attr('width', width) - .attr('height', height) - .append('text') - .attr('transform', function () { - if (div.attr('class') === 'x-axis-title') { - return 'translate(' + width / 2 + ',11)'; - } - return 'translate(11,' + height / 2 + ')rotate(270)'; - }) - .attr('text-anchor', 'middle') - .text(title); - }); + div.append('svg') + .attr('width', width) + .attr('height', height) + .append('text') + .attr('transform', function () { + if (div.attr('class') === 'x-axis-title') { + return 'translate(' + width / 2 + ',11)'; + } + return 'translate(11,' + height / 2 + ')rotate(270)'; + }) + .attr('text-anchor', 'middle') + .text(title); + }); + }; }; - }; + } return AxisTitle; }; diff --git a/src/ui/public/vislib/lib/chart_title.js b/src/ui/public/vislib/lib/chart_title.js index b0442758a8e5b..1159f148eeb29 100644 --- a/src/ui/public/vislib/lib/chart_title.js +++ b/src/ui/public/vislib/lib/chart_title.js @@ -1,12 +1,11 @@ import d3 from 'd3'; -import $ from 'jquery'; import _ from 'lodash'; import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; import VislibComponentsTooltipProvider from 'ui/vislib/components/tooltip'; export default function ChartTitleFactory(Private) { - let ErrorHandler = Private(VislibLibErrorHandlerProvider); - let Tooltip = Private(VislibComponentsTooltipProvider); + const ErrorHandler = Private(VislibLibErrorHandlerProvider); + const Tooltip = Private(VislibComponentsTooltipProvider); /** * Appends chart titles to the visualization @@ -15,117 +14,117 @@ export default function ChartTitleFactory(Private) { * @constructor * @param el {HTMLElement} Reference to DOM element */ - _.class(ChartTitle).inherits(ErrorHandler); - function ChartTitle(el) { - if (!(this instanceof ChartTitle)) { - return new ChartTitle(el); + class ChartTitle extends ErrorHandler { + constructor(el) { + super(); + this.el = el; + this.tooltip = new Tooltip('chart-title', el, function (d) { + 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 = '

' + _.escape(mapLabel) + '

'; + }; + + // label.addTo(this.map); + this.map.addControl(label); + }; - return fitContainer; - }, - onRemove: function (map) { - $(fitContainer).off('click'); + /** + * remove css class for desat filters on map tiles + * + * @method saturateTiles + * @return undefined + */ + saturateTiles() { + if (!this._attr.isDesaturated) { + $('img.leaflet-tile-loaded').addClass('filters-off'); } - }); - - this._fitControl = new FitControl(); - this.map.addControl(this._fitControl); - }; - - /** - * Adds label div to each map when data is split - * - * @method addTitle - * @param mapLabel {String} - * @return {undefined} - */ - TileMapMap.prototype.addTitle = function (mapLabel) { - if (this._label) return; - - let 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 = '

' + _.escape(mapLabel) + '

'; - }; - - // label.addTo(this.map); - this.map.addControl(label); - }; - - /** - * remove css class for desat filters on map tiles - * - * @method saturateTiles - * @return undefined - */ - TileMapMap.prototype.saturateTiles = function () { - if (!this._attr.isDesaturated) { - $('img.leaflet-tile-loaded').addClass('filters-off'); - } - }; - TileMapMap.prototype.updateSize = function () { - this.map.invalidateSize({ - debounceMoveend: true - }); - }; - - TileMapMap.prototype.destroy = function () { - if (this._label) this._label.removeFrom(this.map); - if (this._fitControl) this._fitControl.removeFrom(this.map); - if (this._boundingControl) this._boundingControl.removeFrom(this.map); - if (this._markers) this._markers.destroy(); - this.map.remove(); - this.map = undefined; - }; + updateSize() { + this.map.invalidateSize({ + debounceMoveend: true + }); + }; - /** - * Switch type of data overlay for map: - * creates featurelayer from mapData (geoJson) - * - * @method _addMarkers - */ - TileMapMap.prototype._addMarkers = function () { - if (!this._geoJson) return; - if (this._markers) this._markers.destroy(); - - this._markers = this._createMarkers({ - tooltipFormatter: this._tooltipFormatter, - valueFormatter: this._valueFormatter, - attr: this._attr - }); - - if (this._geoJson.features.length > 1) { - this._markers.addLegend(); - } - }; + destroy() { + if (this._label) this._label.removeFrom(this.map); + if (this._fitControl) this._fitControl.removeFrom(this.map); + if (this._boundingControl) this._boundingControl.removeFrom(this.map); + if (this._markers) this._markers.destroy(); + this.map.remove(); + this.map = undefined; + }; - /** - * Create the marker instance using the given options - * - * @method _createMarkers - * @param options {Object} options to give to marker class - * @return {Object} marker layer - */ - TileMapMap.prototype._createMarkers = function (options) { - let MarkerType = markerTypes[this._markerType]; - return new MarkerType(this.map, this._geoJson, options); - }; + /** + * Switch type of data overlay for map: + * creates featurelayer from mapData (geoJson) + * + * @method _addMarkers + */ + _addMarkers() { + if (!this._geoJson) return; + if (this._markers) this._markers.destroy(); + + this._markers = this._createMarkers({ + tooltipFormatter: this._tooltipFormatter, + valueFormatter: this._valueFormatter, + attr: this._attr + }); - TileMapMap.prototype._attachEvents = function () { - let self = this; - let saturateTiles = self.saturateTiles.bind(self); + if (this._geoJson.features.length > 1) { + this._markers.addLegend(); + } + }; - this._tileLayer.on('tileload', saturateTiles); + /** + * Create the marker instance using the given options + * + * @method _createMarkers + * @param options {Object} options to give to marker class + * @return {Object} marker layer + */ + _createMarkers(options) { + const MarkerType = markerTypes[this._markerType]; + return new MarkerType(this.map, this._geoJson, options); + }; - this.map.on('unload', function () { - self._tileLayer.off('tileload', saturateTiles); - }); + _attachEvents() { + const self = this; + const saturateTiles = self.saturateTiles.bind(self); - this.map.on('moveend', function setZoomCenter(ev) { - if (!self.map) return; - // update internal center and zoom references - const uglyCenter = self.map.getCenter(); - self._mapCenter = [uglyCenter.lat, uglyCenter.lng]; - self._mapZoom = self.map.getZoom(); - self._addMarkers(); + this._tileLayer.on('tileload', saturateTiles); - if (!self._events) return; + this.map.on('unload', function () { + self._tileLayer.off('tileload', saturateTiles); + }); - self._events.emit('mapMoveEnd', { - chart: self._chartData, - map: self.map, - center: self._mapCenter, - zoom: self._mapZoom, + this.map.on('moveend', function setZoomCenter() { + if (!self.map) return; + // update internal center and zoom references + const uglyCenter = self.map.getCenter(); + self._mapCenter = [uglyCenter.lat, uglyCenter.lng]; + self._mapZoom = self.map.getZoom(); + self._addMarkers(); + + if (!self._events) return; + + self._events.emit('mapMoveEnd', { + chart: self._chartData, + map: self.map, + center: self._mapCenter, + zoom: self._mapZoom, + }); }); - }); - this.map.on('draw:created', function (e) { - let drawType = e.layerType; - if (!self._events || !self._events.listenerCount(drawType)) return; + this.map.on('draw:created', function (e) { + const drawType = e.layerType; + if (!self._events || !self._events.listenerCount(drawType)) return; - // TODO: Different drawTypes need differ info. Need a switch on the object creation - let bounds = e.layer.getBounds(); + // TODO: Different drawTypes need differ info. Need a switch on the object creation + const bounds = e.layer.getBounds(); - let SElng = bounds.getSouthEast().lng; - if (SElng > 180) { - SElng -= 360; - } - let NWlng = bounds.getNorthWest().lng; - if (NWlng < -180) { - NWlng += 360; - } - self._events.emit(drawType, { - e: e, - chart: self._chartData, - bounds: { - top_left: { - lat: bounds.getNorthWest().lat, - lon: NWlng - }, - bottom_right: { - lat: bounds.getSouthEast().lat, - lon: SElng - } + let SElng = bounds.getSouthEast().lng; + if (SElng > 180) { + SElng -= 360; + } + let NWlng = bounds.getNorthWest().lng; + if (NWlng < -180) { + NWlng += 360; } + self._events.emit(drawType, { + e: e, + chart: self._chartData, + bounds: { + top_left: { + lat: bounds.getNorthWest().lat, + lon: NWlng + }, + bottom_right: { + lat: bounds.getSouthEast().lat, + lon: SElng + } + } + }); }); - }); - this.map.on('zoomend', function () { - if (!self.map) return; - self._mapZoom = self.map.getZoom(); - if (!self._events) return; + this.map.on('zoomend', function () { + if (!self.map) return; + self._mapZoom = self.map.getZoom(); + if (!self._events) return; - self._events.emit('mapZoomEnd', { - chart: self._chartData, - map: self.map, - zoom: self._mapZoom, + self._events.emit('mapZoomEnd', { + chart: self._chartData, + map: self.map, + zoom: self._mapZoom, + }); }); - }); - }; + }; - TileMapMap.prototype._createMap = function (mapOptions) { - if (this.map) this.destroy(); + _createMap(mapOptions) { + if (this.map) this.destroy(); - // add map tiles layer, using the mapTiles object settings - if (this._attr.wms && this._attr.wms.enabled) { - _.assign(mapOptions, { - minZoom: 1, - maxZoom: 18 - }); - this._tileLayer = L.tileLayer.wms(this._attr.wms.url, this._attr.wms.options); - } else { - this._tileLayer = L.tileLayer(mapTiles.url, mapTiles.options); - } + // add map tiles layer, using the mapTiles object settings + if (this._attr.wms && this._attr.wms.enabled) { + _.assign(mapOptions, { + minZoom: 1, + maxZoom: 18 + }); + this._tileLayer = L.tileLayer.wms(this._attr.wms.url, this._attr.wms.options); + } else { + this._tileLayer = L.tileLayer(mapTiles.url, mapTiles.options); + } - // append tile layers, center and zoom to the map options - mapOptions.layers = this._tileLayer; - mapOptions.center = this._mapCenter; - mapOptions.zoom = this._mapZoom; + // append tile layers, center and zoom to the map options + mapOptions.layers = this._tileLayer; + mapOptions.center = this._mapCenter; + mapOptions.zoom = this._mapZoom; - this.map = L.map(this._container, mapOptions); - this._attachEvents(); - this._addMarkers(); - }; + this.map = L.map(this._container, mapOptions); + this._attachEvents(); + this._addMarkers(); + }; - /** - * zoom map to fit all features in featureLayer - * - * @method _fitBounds - * @param map {Leaflet Object} - * @return {boolean} - */ - TileMapMap.prototype._fitBounds = function () { - this.map.fitBounds(this._getDataRectangles()); - }; + /** + * zoom map to fit all features in featureLayer + * + * @method _fitBounds + * @param map {Leaflet Object} + * @return {boolean} + */ + _fitBounds() { + this.map.fitBounds(this._getDataRectangles()); + }; - /** - * Get the Rectangles representing the geohash grid - * - * @return {LatLngRectangles[]} - */ - TileMapMap.prototype._getDataRectangles = function () { - if (!this._geoJson) return []; - return _.pluck(this._geoJson.features, 'properties.rectangle'); - }; + /** + * Get the Rectangles representing the geohash grid + * + * @return {LatLngRectangles[]} + */ + _getDataRectangles() { + if (!this._geoJson) return []; + return _.pluck(this._geoJson.features, 'properties.rectangle'); + }; + } return TileMapMap; }; diff --git a/src/ui/public/vislib/visualizations/_point_series_chart.js b/src/ui/public/vislib/visualizations/_point_series_chart.js index 31b60787db1dd..7cad5cda2dadc 100644 --- a/src/ui/public/vislib/visualizations/_point_series_chart.js +++ b/src/ui/public/vislib/visualizations/_point_series_chart.js @@ -6,179 +6,171 @@ import errors from 'ui/errors'; export default function PointSeriesChartProvider(Private) { - let Chart = Private(VislibVisualizationsChartProvider); - let Tooltip = Private(VislibComponentsTooltipProvider); - let touchdownTmpl = _.template(require('ui/vislib/partials/touchdown.tmpl.html')); - - _.class(PointSeriesChart).inherits(Chart); - function PointSeriesChart(handler, chartEl, chartData) { - if (!(this instanceof PointSeriesChart)) { - return new PointSeriesChart(handler, chartEl, chartData); + const Chart = Private(VislibVisualizationsChartProvider); + const Tooltip = Private(VislibComponentsTooltipProvider); + const touchdownTmpl = _.template(require('ui/vislib/partials/touchdown.tmpl.html')); + + class PointSeriesChart extends Chart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); } - PointSeriesChart.Super.apply(this, arguments); - } + _stackMixedValues(stackCount) { + let currentStackOffsets = [0, 0]; + let currentStackIndex = 0; - PointSeriesChart.prototype._stackMixedValues = function (stackCount) { - let currentStackOffsets = [0, 0]; - let currentStackIndex = 0; + return function (d, y0, y) { + const firstStack = currentStackIndex % stackCount === 0; + const lastStack = ++currentStackIndex === stackCount; - return function (d, y0, y) { - let firstStack = currentStackIndex % stackCount === 0; - let lastStack = ++currentStackIndex === stackCount; + if (firstStack) { + currentStackOffsets = [0, 0]; + } + + if (lastStack) currentStackIndex = 0; + + if (y >= 0) { + d.y0 = currentStackOffsets[1]; + currentStackOffsets[1] += y; + } else { + d.y0 = currentStackOffsets[0]; + currentStackOffsets[0] += y; + } + }; + }; + + /** + * Stacks chart data values + * + * @method stackData + * @param data {Object} Elasticsearch query result for this chart + * @returns {Array} Stacked data objects with x, y, and y0 values + */ + stackData(data) { + const self = this; + const isHistogram = (this._attr.type === 'histogram' && this._attr.mode === 'stacked'); + const stack = this._attr.stack; + + if (isHistogram) stack.out(self._stackMixedValues(data.series.length)); + + return stack(data.series.map(function (d) { + const label = d.label; + return d.values.map(function (e, i) { + return { + _input: e, + label: label, + x: self._attr.xValue.call(d.values, e, i), + y: self._attr.yValue.call(d.values, e, i) + }; + }); + })); + }; - if (firstStack) { - currentStackOffsets = [0, 0]; - } - if (lastStack) currentStackIndex = 0; + validateDataCompliesWithScalingMethod(data) { + const valuesSmallerThanOne = function (d) { + return d.values && d.values.some(e => e.y < 1); + }; - if (y >= 0) { - d.y0 = currentStackOffsets[1]; - currentStackOffsets[1] += y; - } else { - d.y0 = currentStackOffsets[0]; - currentStackOffsets[0] += y; + const invalidLogScale = data.series && data.series.some(valuesSmallerThanOne); + if (this._attr.scale === 'log' && invalidLogScale) { + throw new errors.InvalidLogScaleValues(); } }; - }; - - /** - * Stacks chart data values - * - * @method stackData - * @param data {Object} Elasticsearch query result for this chart - * @returns {Array} Stacked data objects with x, y, and y0 values - */ - PointSeriesChart.prototype.stackData = function (data) { - let self = this; - let isHistogram = (this._attr.type === 'histogram' && this._attr.mode === 'stacked'); - let stack = this._attr.stack; - - if (isHistogram) stack.out(self._stackMixedValues(data.series.length)); - - return stack(data.series.map(function (d) { - let label = d.label; - return d.values.map(function (e, i) { - return { - _input: e, - label: label, - x: self._attr.xValue.call(d.values, e, i), - y: self._attr.yValue.call(d.values, e, i) - }; - }); - })); - }; + /** + * Creates rects to show buckets outside of the ordered.min and max, returns rects + * + * @param xScale {Function} D3 xScale function + * @param svg {HTMLElement} Reference to SVG + * @method createEndZones + * @returns {D3.Selection} + */ + createEndZones(svg) { + const self = this; + const xAxis = this.handler.xAxis; + const xScale = xAxis.xScale; + const ordered = xAxis.ordered; + const missingMinMax = !ordered || _.isUndefined(ordered.min) || _.isUndefined(ordered.max); + + if (missingMinMax || ordered.endzones === false) return; + + const attr = this.handler._attr; + const height = attr.height; + const width = attr.width; + const margin = attr.margin; + + // we don't want to draw endzones over our min and max values, they + // are still a part of the dataset. We want to start the endzones just + // outside of them so we will use these values rather than ordered.min/max + const oneUnit = (ordered.units || _.identity)(1); + + // points on this axis represent the amount of time they cover, + // so draw the endzones at the actual time bounds + const leftEndzone = { + x: 0, + w: Math.max(xScale(ordered.min), 0) + }; - PointSeriesChart.prototype.validateDataCompliesWithScalingMethod = function (data) { - const invalidLogScale = data.series && data.series.some(valuesSmallerThanOne); - if (this._attr.scale === 'log' && invalidLogScale) { - throw new errors.InvalidLogScaleValues(); - } - }; - function valuesSmallerThanOne(d) { - return d.values && d.values.some(e => e.y < 1); - } + const rightLastVal = xAxis.expandLastBucket ? ordered.max : Math.min(ordered.max, _.last(xAxis.xValues)); + const rightStart = rightLastVal + oneUnit; + const rightEndzone = { + x: xScale(rightStart), + w: Math.max(width - xScale(rightStart), 0) + }; + this.endzones = svg.selectAll('.layer') + .data([leftEndzone, rightEndzone]) + .enter() + .insert('g', '.brush') + .attr('class', 'endzone') + .append('rect') + .attr('class', 'zone') + .attr('x', function (d) { + return d.x; + }) + .attr('y', 0) + .attr('height', height - margin.top - margin.bottom) + .attr('width', function (d) { + return d.w; + }); + function callPlay(event) { + const boundData = event.target.__data__; + const mouseChartXCoord = event.clientX - self.chartEl.getBoundingClientRect().left; + const wholeBucket = boundData && boundData.x != null; - /** - * Creates rects to show buckets outside of the ordered.min and max, returns rects - * - * @param xScale {Function} D3 xScale function - * @param svg {HTMLElement} Reference to SVG - * @method createEndZones - * @returns {D3.Selection} - */ - PointSeriesChart.prototype.createEndZones = function (svg) { - let self = this; - let xAxis = this.handler.xAxis; - let xScale = xAxis.xScale; - let ordered = xAxis.ordered; - let missingMinMax = !ordered || _.isUndefined(ordered.min) || _.isUndefined(ordered.max); - - if (missingMinMax || ordered.endzones === false) return; - - let attr = this.handler._attr; - let height = attr.height; - let width = attr.width; - let margin = attr.margin; - let color = '#004c99'; - - // we don't want to draw endzones over our min and max values, they - // are still a part of the dataset. We want to start the endzones just - // outside of them so we will use these values rather than ordered.min/max - let oneUnit = (ordered.units || _.identity)(1); - let beyondMin = ordered.min - oneUnit; - let beyondMax = ordered.max + oneUnit; - - // points on this axis represent the amount of time they cover, - // so draw the endzones at the actual time bounds - let leftEndzone = { - x: 0, - w: Math.max(xScale(ordered.min), 0) - }; + // the min and max that the endzones start in + const min = leftEndzone.w; + const max = rightEndzone.x; - let rightLastVal = xAxis.expandLastBucket ? ordered.max : Math.min(ordered.max, _.last(xAxis.xValues)); - let rightStart = rightLastVal + oneUnit; - let rightEndzone = { - x: xScale(rightStart), - w: Math.max(width - xScale(rightStart), 0) - }; + // bounds of the cursor to consider + let xLeft = mouseChartXCoord; + let xRight = mouseChartXCoord; + if (wholeBucket) { + xLeft = xScale(boundData.x); + xRight = xScale(xAxis.addInterval(boundData.x)); + } - this.endzones = svg.selectAll('.layer') - .data([leftEndzone, rightEndzone]) - .enter() - .insert('g', '.brush') - .attr('class', 'endzone') - .append('rect') - .attr('class', 'zone') - .attr('x', function (d) { - return d.x; - }) - .attr('y', 0) - .attr('height', height - margin.top - margin.bottom) - .attr('width', function (d) { - return d.w; - }); - - function callPlay(event) { - let boundData = event.target.__data__; - let mouseChartXCoord = event.clientX - self.chartEl.getBoundingClientRect().left; - let wholeBucket = boundData && boundData.x != null; - - // the min and max that the endzones start in - let min = leftEndzone.w; - let max = rightEndzone.x; - - // bounds of the cursor to consider - let xLeft = mouseChartXCoord; - let xRight = mouseChartXCoord; - if (wholeBucket) { - xLeft = xScale(boundData.x); - xRight = xScale(xAxis.addInterval(boundData.x)); + return { + wholeBucket: wholeBucket, + touchdown: min > xLeft || max < xRight + }; } + function textFormatter() { + return touchdownTmpl(callPlay(d3.event)); + } - return { - wholeBucket: wholeBucket, - touchdown: min > xLeft || max < xRight + const endzoneTT = new Tooltip('endzones', this.handler.el, textFormatter, null); + this.tooltips.push(endzoneTT); + endzoneTT.order = 0; + endzoneTT.showCondition = function inEndzone() { + return callPlay(d3.event).touchdown; }; - } - - function textFormatter() { - return touchdownTmpl(callPlay(d3.event)); - } - - let endzoneTT = new Tooltip('endzones', this.handler.el, textFormatter, null); - this.tooltips.push(endzoneTT); - endzoneTT.order = 0; - endzoneTT.showCondition = function inEndzone() { - return callPlay(d3.event).touchdown; + endzoneTT.render()(svg); }; - endzoneTT.render()(svg); - }; + } return PointSeriesChart; }; diff --git a/src/ui/public/vislib/visualizations/area_chart.js b/src/ui/public/vislib/visualizations/area_chart.js index 4b0d1d1f48835..c7599800d4c25 100644 --- a/src/ui/public/vislib/visualizations/area_chart.js +++ b/src/ui/public/vislib/visualizations/area_chart.js @@ -6,8 +6,8 @@ import VislibVisualizationsPointSeriesChartProvider from 'ui/vislib/visualizatio import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; export default function AreaChartFactory(Private) { - let PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); - let TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); + const PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); + const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); /** * Area chart visualization @@ -20,331 +20,322 @@ export default function AreaChartFactory(Private) { * @param chartData {Object} Elasticsearch query results for this specific * chart */ - _.class(AreaChart).inherits(PointSeriesChart); - function AreaChart(handler, chartEl, chartData) { - if (!(this instanceof AreaChart)) { - return new AreaChart(handler, chartEl, chartData); - } - - AreaChart.Super.apply(this, arguments); + class AreaChart extends PointSeriesChart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); + + this.isOverlapping = (handler._attr.mode === 'overlap'); + + if (this.isOverlapping) { + + // Default opacity should return to 0.6 on mouseout + const defaultOpacity = 0.6; + handler._attr.defaultOpacity = defaultOpacity; + handler.highlight = function (element) { + const label = this.getAttribute('data-label'); + if (!label) return; + + const highlightOpacity = 0.8; + const highlightElements = $('[data-label]', element.parentNode).filter( + function (els, el) { + return `${$(el).data('label')}` === label; + }); + $('[data-label]', element.parentNode).not(highlightElements).css('opacity', defaultOpacity / 2); // half of the default opacity + highlightElements.css('opacity', highlightOpacity); + }; + handler.unHighlight = function (element) { + $('[data-label]', element).css('opacity', defaultOpacity); + + //The legend should keep max opacity + $('[data-label]', $(element).siblings()).css('opacity', 1); + }; + } - this.isOverlapping = (handler._attr.mode === 'overlap'); + this.checkIfEnoughData(); - if (this.isOverlapping) { + this._attr = _.defaults(handler._attr || {}, { + xValue: function (d) { + return d.x; + }, + yValue: function (d) { + return d.y; + } + }); + } - // Default opacity should return to 0.6 on mouseout - let defaultOpacity = 0.6; - handler._attr.defaultOpacity = defaultOpacity; - handler.highlight = function (element) { - let label = this.getAttribute('data-label'); - if (!label) return; + /** + * Adds SVG path to area chart + * + * @method addPath + * @param svg {HTMLElement} SVG to which rect are appended + * @param layers {Array} Chart data array + * @returns {D3.UpdateSelection} SVG with path added + */ + addPath(svg, layers) { + const ordered = this.handler.data.get('ordered'); + const isTimeSeries = (ordered && ordered.date); + const isOverlapping = this.isOverlapping; + const color = this.handler.data.getColorFunc(); + const xScale = this.handler.xAxis.xScale; + const yScale = this.handler.yAxis.yScale; + const interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; + const area = d3.svg.area() + .x(function (d) { + if (isTimeSeries) { + return xScale(d.x); + } + return xScale(d.x) + xScale.rangeBand() / 2; + }) + .y0(function (d) { + if (isOverlapping) { + return yScale(0); + } - let highlightOpacity = 0.8; - let highlightElements = $('[data-label]', element.parentNode).filter( - function (els, el) { - return `${$(el).data('label')}` === label; - }); - $('[data-label]', element.parentNode).not(highlightElements).css('opacity', defaultOpacity / 2); // half of the default opacity - highlightElements.css('opacity', highlightOpacity); - }; - handler.unHighlight = function (element) { - $('[data-label]', element).css('opacity', defaultOpacity); + return yScale(d.y0); + }) + .y1(function (d) { + if (isOverlapping) { + return yScale(d.y); + } - //The legend should keep max opacity - $('[data-label]', $(element).siblings()).css('opacity', 1); - }; - } + return yScale(d.y0 + d.y); + }) + .defined(function (d) { + return !_.isNull(d.y); + }) + .interpolate(interpolate); + + // Data layers + const layer = svg.selectAll('.layer') + .data(layers) + .enter() + .append('g') + .attr('class', function (d, i) { + return 'pathgroup ' + i; + }); - this.checkIfEnoughData(); + // Append path + const path = layer.append('path') + .call(this._addIdentifier) + .style('fill', function (d) { + return color(d[0].label); + }) + .classed('overlap_area', function () { + return isOverlapping; + }); - this._attr = _.defaults(handler._attr || {}, { - xValue: function (d) { return d.x; }, - yValue: function (d) { return d.y; } - }); - } + // update + path.attr('d', function (d) { + return area(d); + }); - /** - * Adds SVG path to area chart - * - * @method addPath - * @param svg {HTMLElement} SVG to which rect are appended - * @param layers {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with path added - */ - AreaChart.prototype.addPath = function (svg, layers) { - let self = this; - let ordered = this.handler.data.get('ordered'); - let isTimeSeries = (ordered && ordered.date); - let isOverlapping = this.isOverlapping; - let color = this.handler.data.getColorFunc(); - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.yAxis.yScale; - let interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; - let area = d3.svg.area() - .x(function (d) { - if (isTimeSeries) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - }) - .y0(function (d) { - if (isOverlapping) { - return yScale(0); - } + return path; + }; - return yScale(d.y0); - }) - .y1(function (d) { - if (isOverlapping) { - return yScale(d.y); + /** + * Adds Events to SVG circles + * + * @method addCircleEvents + * @param element {D3.UpdateSelection} SVG circles + * @returns {D3.Selection} circles with event listeners attached + */ + addCircleEvents(element, svg) { + const events = this.events; + const isBrushable = events.isBrushable(); + const brush = isBrushable ? events.addBrushEvent(svg) : undefined; + const hover = events.addHoverEvent(); + const mouseout = events.addMouseoutEvent(); + const click = events.addClickEvent(); + const attachedEvents = element.call(hover).call(mouseout).call(click); + + if (isBrushable) { + attachedEvents.call(brush); } - return yScale(d.y0 + d.y); - }) - .defined(function (d) { return !_.isNull(d.y); }) - .interpolate(interpolate); - - // Data layers - let layer = svg.selectAll('.layer') - .data(layers) - .enter() - .append('g') - .attr('class', function (d, i) { - return 'pathgroup ' + i; - }); - - // Append path - let path = layer.append('path') - .call(this._addIdentifier) - .style('fill', function (d) { - return color(d[0].label); - }) - .classed('overlap_area', function () { - return isOverlapping; - }); - - // update - path.attr('d', function (d) { - return area(d); - }); - - return path; - }; - - /** - * Adds Events to SVG circles - * - * @method addCircleEvents - * @param element {D3.UpdateSelection} SVG circles - * @returns {D3.Selection} circles with event listeners attached - */ - AreaChart.prototype.addCircleEvents = function (element, svg) { - let events = this.events; - let isBrushable = events.isBrushable(); - let brush = isBrushable ? events.addBrushEvent(svg) : undefined; - let hover = events.addHoverEvent(); - let mouseout = events.addMouseoutEvent(); - let click = events.addClickEvent(); - let attachedEvents = element.call(hover).call(mouseout).call(click); - - if (isBrushable) { - attachedEvents.call(brush); - } - - return attachedEvents; - }; + return attachedEvents; + }; - /** - * Adds SVG circles to area chart - * - * @method addCircles - * @param svg {HTMLElement} SVG to which circles are appended - * @param data {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with circles added - */ - AreaChart.prototype.addCircles = function (svg, data) { - let self = this; - let color = this.handler.data.getColorFunc(); - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.yAxis.yScale; - let ordered = this.handler.data.get('ordered'); - let circleRadius = 12; - let circleStrokeWidth = 0; - let tooltip = this.tooltip; - let isTooltip = this._attr.addTooltip; - let isOverlapping = this.isOverlapping; - let layer; - let circles; - - layer = svg.selectAll('.points') - .data(data) - .enter() + /** + * Adds SVG circles to area chart + * + * @method addCircles + * @param svg {HTMLElement} SVG to which circles are appended + * @param data {Array} Chart data array + * @returns {D3.UpdateSelection} SVG with circles added + */ + addCircles(svg, data) { + const color = this.handler.data.getColorFunc(); + const xScale = this.handler.xAxis.xScale; + const yScale = this.handler.yAxis.yScale; + const ordered = this.handler.data.get('ordered'); + const circleRadius = 12; + const circleStrokeWidth = 0; + const tooltip = this.tooltip; + const isTooltip = this._attr.addTooltip; + const isOverlapping = this.isOverlapping; + + const layer = svg.selectAll('.points') + .data(data) + .enter() .append('g') .attr('class', 'points area'); - // append the circles - circles = layer - .selectAll('circles') - .data(function appendData(data) { - return data.filter(function isZeroOrNull(d) { - return d.y !== 0 && !_.isNull(d.y); + // append the circles + const circles = layer + .selectAll('circles') + .data(function appendData(data) { + return data.filter(function isZeroOrNull(d) { + return d.y !== 0 && !_.isNull(d.y); + }); }); - }); - - // exit - circles.exit().remove(); - - // enter - circles - .enter() - .append('circle') - .call(this._addIdentifier) - .attr('stroke', function strokeColor(d) { - return color(d.label); - }) - .attr('fill', 'transparent') - .attr('stroke-width', circleStrokeWidth); - - // update - circles - .attr('cx', function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - }) - .attr('cy', function cy(d) { - if (isOverlapping) { - return yScale(d.y); - } - return yScale(d.y0 + d.y); - }) - .attr('r', circleRadius); - - // Add tooltip - if (isTooltip) { - circles.call(tooltip.render()); - } - - return circles; - }; - /** - * Adds SVG clipPath - * - * @method addClipPath - * @param svg {HTMLElement} SVG to which clipPath is appended - * @param width {Number} SVG width - * @param height {Number} SVG height - * @returns {D3.UpdateSelection} SVG with clipPath added - */ - AreaChart.prototype.addClipPath = function (svg, width, height) { - // Prevents circles from being clipped at the top of the chart - let startX = 0; - let startY = 0; - let id = 'chart-area' + _.uniqueId(); - - // Creating clipPath - return svg - .attr('clip-path', 'url(#' + id + ')') - .append('clipPath') - .attr('id', id) - .append('rect') - .attr('x', startX) - .attr('y', startY) - .attr('width', width) - .attr('height', height); - }; - - AreaChart.prototype.checkIfEnoughData = function () { - let series = this.chartData.series; - let message = 'Area charts require more than one data point. Try adding ' + - 'an X-Axis Aggregation'; - - let notEnoughData = series.some(function (obj) { - return obj.values.length < 2; - }); - - if (notEnoughData) { - throw new errors.NotEnoughData(message); - } - }; - - AreaChart.prototype.validateWiggleSelection = function () { - let isWiggle = this._attr.mode === 'wiggle'; - let ordered = this.handler.data.get('ordered'); + // exit + circles.exit().remove(); + + // enter + circles + .enter() + .append('circle') + .call(this._addIdentifier) + .attr('stroke', function strokeColor(d) { + return color(d.label); + }) + .attr('fill', 'transparent') + .attr('stroke-width', circleStrokeWidth); + + // update + circles + .attr('cx', function cx(d) { + if (ordered && ordered.date) { + return xScale(d.x); + } + return xScale(d.x) + xScale.rangeBand() / 2; + }) + .attr('cy', function cy(d) { + if (isOverlapping) { + return yScale(d.y); + } + return yScale(d.y0 + d.y); + }) + .attr('r', circleRadius); - if (isWiggle && !ordered) throw new errors.InvalidWiggleSelection(); - }; + // Add tooltip + if (isTooltip) { + circles.call(tooltip.render()); + } - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the area chart - */ - AreaChart.prototype.draw = function () { - // Attributes - let self = this; - let xScale = this.handler.xAxis.xScale; - let $elem = $(this.chartEl); - let margin = this._attr.margin; - let elWidth = this._attr.width = $elem.width(); - let elHeight = this._attr.height = $elem.height(); - let yMin = this.handler.yAxis.yMin; - let yScale = this.handler.yAxis.yScale; - let minWidth = 20; - let minHeight = 20; - let addTimeMarker = this._attr.addTimeMarker; - let times = this._attr.times || []; - let timeMarker; - let div; - let svg; - let width; - let height; - let layers; - let circles; - let path; - - return function (selection) { - selection.each(function (data) { - // Stack data - layers = self.stackData(data); - - // Get the width and height - width = elWidth; - height = elHeight - margin.top - margin.bottom; - - if (addTimeMarker) { - timeMarker = new TimeMarker(times, xScale, height); - } + return circles; + }; - if (width < minWidth || height < minHeight) { - throw new errors.ContainerTooSmall(); - } - self.validateWiggleSelection(); + /** + * Adds SVG clipPath + * + * @method addClipPath + * @param svg {HTMLElement} SVG to which clipPath is appended + * @param width {Number} SVG width + * @param height {Number} SVG height + * @returns {D3.UpdateSelection} SVG with clipPath added + */ + addClipPath(svg, width, height) { + // Prevents circles from being clipped at the top of the chart + const startX = 0; + const startY = 0; + const id = 'chart-area' + _.uniqueId(); + + // Creating clipPath + return svg + .attr('clip-path', 'url(#' + id + ')') + .append('clipPath') + .attr('id', id) + .append('rect') + .attr('x', startX) + .attr('y', startY) + .attr('width', width) + .attr('height', height); + }; - // Select the current DOM element - div = d3.select(this); + checkIfEnoughData() { + const series = this.chartData.series; + const message = 'Area charts require more than one data point. Try adding ' + + 'an X-Axis Aggregation'; - // Create the canvas for the visualization - svg = div.append('svg') - .attr('width', width) - .attr('height', height + margin.top + margin.bottom) - .append('g') - .attr('transform', 'translate(0,' + margin.top + ')'); + const notEnoughData = series.some(function (obj) { + return obj.values.length < 2; + }); - // add clipPath to hide circles when they go out of bounds - self.addClipPath(svg, width, height); - self.createEndZones(svg); + if (notEnoughData) { + throw new errors.NotEnoughData(message); + } + }; - // add path - path = self.addPath(svg, layers); + validateWiggleSelection() { + const isWiggle = this._attr.mode === 'wiggle'; + const ordered = this.handler.data.get('ordered'); - if (yMin < 0 && self._attr.mode !== 'wiggle' && self._attr.mode !== 'silhouette') { + if (isWiggle && !ordered) throw new errors.InvalidWiggleSelection(); + }; - // Draw line at yScale 0 value - svg.append('line') + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the area chart + */ + draw() { + // Attributes + const self = this; + const xScale = this.handler.xAxis.xScale; + const $elem = $(this.chartEl); + const margin = this._attr.margin; + const elWidth = this._attr.width = $elem.width(); + const elHeight = this._attr.height = $elem.height(); + const yMin = this.handler.yAxis.yMin; + const yScale = this.handler.yAxis.yScale; + const minWidth = 20; + const minHeight = 20; + const addTimeMarker = this._attr.addTimeMarker; + const times = this._attr.times || []; + let timeMarker; + + return function (selection) { + selection.each(function (data) { + // Stack data + const layers = self.stackData(data); + + // Get the width and height + const width = elWidth; + const height = elHeight - margin.top - margin.bottom; + + if (addTimeMarker) { + timeMarker = new TimeMarker(times, xScale, height); + } + + if (width < minWidth || height < minHeight) { + throw new errors.ContainerTooSmall(); + } + self.validateWiggleSelection(); + + // Select the current DOM element + const div = d3.select(this); + + // Create the canvas for the visualization + const svg = div.append('svg') + .attr('width', width) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(0,' + margin.top + ')'); + + // add clipPath to hide circles when they go out of bounds + self.addClipPath(svg, width, height); + self.createEndZones(svg); + + // add path + self.addPath(svg, layers); + + if (yMin < 0 && self._attr.mode !== 'wiggle' && self._attr.mode !== 'silhouette') { + + // Draw line at yScale 0 value + svg.append('line') .attr('class', 'zero-line') .attr('x1', 0) .attr('y1', yScale(0)) @@ -352,32 +343,33 @@ export default function AreaChartFactory(Private) { .attr('y2', yScale(0)) .style('stroke', '#ddd') .style('stroke-width', 1); - } - - // add circles - circles = self.addCircles(svg, layers); + } - // add click and hover events to circles - self.addCircleEvents(circles, svg); + // add circles + const circles = self.addCircles(svg, layers); - // chart base line - let line = svg.append('line') - .attr('class', 'base-line') - .attr('x1', 0) - .attr('y1', yScale(0)) - .attr('x2', width) - .attr('y2', yScale(0)) - .style('stroke', '#ddd') - .style('stroke-width', 1); - - if (addTimeMarker) { - timeMarker.render(svg); - } + // add click and hover events to circles + self.addCircleEvents(circles, svg); - return svg; - }); + // chart base line + svg.append('line') + .attr('class', 'base-line') + .attr('x1', 0) + .attr('y1', yScale(0)) + .attr('x2', width) + .attr('y2', yScale(0)) + .style('stroke', '#ddd') + .style('stroke-width', 1); + + if (addTimeMarker) { + timeMarker.render(svg); + } + + return svg; + }); + }; }; - }; + } return AreaChart; }; diff --git a/src/ui/public/vislib/visualizations/column_chart.js b/src/ui/public/vislib/visualizations/column_chart.js index b4c8b220d37c2..172fef57fd57b 100644 --- a/src/ui/public/vislib/visualizations/column_chart.js +++ b/src/ui/public/vislib/visualizations/column_chart.js @@ -7,8 +7,8 @@ import VislibVisualizationsPointSeriesChartProvider from 'ui/vislib/visualizatio import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; export default function ColumnChartFactory(Private) { - let PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); - let TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); + const PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); + const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); /** * Vertical Bar Chart Visualization: renders vertical and/or stacked bars @@ -20,313 +20,306 @@ export default function ColumnChartFactory(Private) { * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ - _.class(ColumnChart).inherits(PointSeriesChart); - function ColumnChart(handler, chartEl, chartData) { - if (!(this instanceof ColumnChart)) { - return new ColumnChart(handler, chartEl, chartData); - } - - ColumnChart.Super.apply(this, arguments); - - // Column chart specific attributes - this._attr = _.defaults(handler._attr || {}, { - xValue: function (d) { return d.x; }, - yValue: function (d) { return d.y; } - }); - } - - /** - * Adds SVG rect to Vertical Bar Chart - * - * @method addBars - * @param svg {HTMLElement} SVG to which rect are appended - * @param layers {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with rect added - */ - ColumnChart.prototype.addBars = function (svg, layers) { - let self = this; - let color = this.handler.data.getColorFunc(); - let tooltip = this.tooltip; - let isTooltip = this._attr.addTooltip; - let layer; - let bars; - - layer = svg.selectAll('.layer') - .data(layers) - .enter().append('g') - .attr('class', function (d, i) { - return 'series ' + i; - }); - - bars = layer.selectAll('rect') - .data(function (d) { - return d; - }); - - bars - .exit() - .remove(); - - bars - .enter() - .append('rect') - .call(this._addIdentifier) - .attr('fill', function (d) { - return color(d.label); - }); - - self.updateBars(bars); - - // Add tooltip - if (isTooltip) { - bars.call(tooltip.render()); - } - - return bars; - }; - - /** - * Determines whether bars are grouped or stacked and updates the D3 - * selection - * - * @method updateBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - ColumnChart.prototype.updateBars = function (bars) { - let offset = this._attr.mode; - - if (offset === 'grouped') { - return this.addGroupedBars(bars); - } - return this.addStackedBars(bars); - }; - - /** - * Adds stacked bars to column chart visualization - * - * @method addStackedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - ColumnChart.prototype.addStackedBars = function (bars) { - let data = this.chartData; - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.yAxis.yScale; - let height = yScale.range()[0]; - let yMin = this.handler.yAxis.yScale.domain()[0]; - - let barWidth; - if (data.ordered && data.ordered.date) { - let start = data.ordered.min; - let end = moment(data.ordered.min).add(data.ordered.interval).valueOf(); - - barWidth = xScale(end) - xScale(start); - barWidth = barWidth - Math.min(barWidth * 0.25, 15); + class ColumnChart extends PointSeriesChart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); + + // Column chart specific attributes + this._attr = _.defaults(handler._attr || {}, { + xValue: function (d) { + return d.x; + }, + yValue: function (d) { + return d.y; + } + }); } - // update - bars - .attr('x', function (d) { - return xScale(d.x); - }) - .attr('width', function () { - return barWidth || xScale.rangeBand(); - }) - .attr('y', function (d) { - if (d.y < 0) { - return yScale(d.y0); + /** + * Adds SVG rect to Vertical Bar Chart + * + * @method addBars + * @param svg {HTMLElement} SVG to which rect are appended + * @param layers {Array} Chart data array + * @returns {D3.UpdateSelection} SVG with rect added + */ + addBars(svg, layers) { + const self = this; + const color = this.handler.data.getColorFunc(); + const tooltip = this.tooltip; + const isTooltip = this._attr.addTooltip; + + const layer = svg.selectAll('.layer') + .data(layers) + .enter().append('g') + .attr('class', function (d, i) { + return 'series ' + i; + }); + + const bars = layer.selectAll('rect') + .data(function (d) { + return d; + }); + + bars + .exit() + .remove(); + + bars + .enter() + .append('rect') + .call(this._addIdentifier) + .attr('fill', function (d) { + return color(d.label); + }); + + self.updateBars(bars); + + // Add tooltip + if (isTooltip) { + bars.call(tooltip.render()); } - return yScale(d.y0 + d.y); - }) - .attr('height', function (d) { - if (d.y < 0) { - return Math.abs(yScale(d.y0 + d.y) - yScale(d.y0)); - } + return bars; + }; - // Due to an issue with D3 not returning zeros correctly when using - // an offset='expand', need to add conditional statement to handle zeros - // appropriately - if (d._input.y === 0) { - return 0; + /** + * Determines whether bars are grouped or stacked and updates the D3 + * selection + * + * @method updateBars + * @param bars {D3.UpdateSelection} SVG with rect added + * @returns {D3.UpdateSelection} + */ + updateBars(bars) { + const offset = this._attr.mode; + + if (offset === 'grouped') { + return this.addGroupedBars(bars); } + return this.addStackedBars(bars); + }; - // for split bars or for one series, - // last series will have d.y0 = 0 - if (d.y0 === 0 && yMin > 0) { - return yScale(yMin) - yScale(d.y); + /** + * Adds stacked bars to column chart visualization + * + * @method addStackedBars + * @param bars {D3.UpdateSelection} SVG with rect added + * @returns {D3.UpdateSelection} + */ + addStackedBars(bars) { + const data = this.chartData; + const xScale = this.handler.xAxis.xScale; + const yScale = this.handler.yAxis.yScale; + const height = yScale.range()[0]; + const yMin = this.handler.yAxis.yScale.domain()[0]; + + let barWidth; + if (data.ordered && data.ordered.date) { + const start = data.ordered.min; + const end = moment(data.ordered.min).add(data.ordered.interval).valueOf(); + + barWidth = xScale(end) - xScale(start); + barWidth = barWidth - Math.min(barWidth * 0.25, 15); } - return yScale(d.y0) - yScale(d.y0 + d.y); - }); - - return bars; - }; + // update + bars + .attr('x', function (d) { + return xScale(d.x); + }) + .attr('width', function () { + return barWidth || xScale.rangeBand(); + }) + .attr('y', function (d) { + if (d.y < 0) { + return yScale(d.y0); + } + + return yScale(d.y0 + d.y); + }) + .attr('height', function (d) { + if (d.y < 0) { + return Math.abs(yScale(d.y0 + d.y) - yScale(d.y0)); + } + + // Due to an issue with D3 not returning zeros correctly when using + // an offset='expand', need to add conditional statement to handle zeros + // appropriately + if (d._input.y === 0) { + return 0; + } + + // for split bars or for one series, + // last series will have d.y0 = 0 + if (d.y0 === 0 && yMin > 0) { + return yScale(yMin) - yScale(d.y); + } + + return yScale(d.y0) - yScale(d.y0 + d.y); + }); + + return bars; + }; - /** - * Adds grouped bars to column chart visualization - * - * @method addGroupedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - ColumnChart.prototype.addGroupedBars = function (bars) { - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.yAxis.yScale; - let data = this.chartData; - let n = data.series.length; - let height = yScale.range()[0]; - let groupSpacingPercentage = 0.15; - let isTimeScale = (data.ordered && data.ordered.date); - let minWidth = 1; - let barWidth; - - // update - bars - .attr('x', function (d, i, j) { - if (isTimeScale) { - let groupWidth = xScale(data.ordered.min + data.ordered.interval) - - xScale(data.ordered.min); - let groupSpacing = groupWidth * groupSpacingPercentage; - - barWidth = (groupWidth - groupSpacing) / n; - - return xScale(d.x) + barWidth * j; - } - return xScale(d.x) + xScale.rangeBand() / n * j; - }) - .attr('width', function () { - if (barWidth < minWidth) { - throw new errors.ContainerTooSmall(); - } + /** + * Adds grouped bars to column chart visualization + * + * @method addGroupedBars + * @param bars {D3.UpdateSelection} SVG with rect added + * @returns {D3.UpdateSelection} + */ + addGroupedBars(bars) { + const xScale = this.handler.xAxis.xScale; + const yScale = this.handler.yAxis.yScale; + const data = this.chartData; + const n = data.series.length; + const height = yScale.range()[0]; + const groupSpacingPercentage = 0.15; + const isTimeScale = (data.ordered && data.ordered.date); + const minWidth = 1; + let barWidth; + + // update + bars + .attr('x', function (d, i, j) { + if (isTimeScale) { + const groupWidth = xScale(data.ordered.min + data.ordered.interval) - + xScale(data.ordered.min); + const groupSpacing = groupWidth * groupSpacingPercentage; + + barWidth = (groupWidth - groupSpacing) / n; + + return xScale(d.x) + barWidth * j; + } + return xScale(d.x) + xScale.rangeBand() / n * j; + }) + .attr('width', function () { + if (barWidth < minWidth) { + throw new errors.ContainerTooSmall(); + } + + if (isTimeScale) { + return barWidth; + } + return xScale.rangeBand() / n; + }) + .attr('y', function (d) { + if (d.y < 0) { + return yScale(0); + } + + return yScale(d.y); + }) + .attr('height', function (d) { + return Math.abs(yScale(0) - yScale(d.y)); + }); + + return bars; + }; - if (isTimeScale) { - return barWidth; - } - return xScale.rangeBand() / n; - }) - .attr('y', function (d) { - if (d.y < 0) { - return yScale(0); + /** + * Adds Events to SVG rect + * Visualization is only brushable when a brush event is added + * If a brush event is added, then a function should be returned. + * + * @method addBarEvents + * @param element {D3.UpdateSelection} target + * @param svg {D3.UpdateSelection} chart SVG + * @returns {D3.Selection} rect with event listeners attached + */ + addBarEvents(element, svg) { + const events = this.events; + const isBrushable = events.isBrushable(); + const brush = isBrushable ? events.addBrushEvent(svg) : undefined; + const hover = events.addHoverEvent(); + const mouseout = events.addMouseoutEvent(); + const click = events.addClickEvent(); + const attachedEvents = element.call(hover).call(mouseout).call(click); + + if (isBrushable) { + attachedEvents.call(brush); } - return yScale(d.y); - }) - .attr('height', function (d) { - return Math.abs(yScale(0) - yScale(d.y)); - }); - - return bars; - }; - - /** - * Adds Events to SVG rect - * Visualization is only brushable when a brush event is added - * If a brush event is added, then a function should be returned. - * - * @method addBarEvents - * @param element {D3.UpdateSelection} target - * @param svg {D3.UpdateSelection} chart SVG - * @returns {D3.Selection} rect with event listeners attached - */ - ColumnChart.prototype.addBarEvents = function (element, svg) { - let events = this.events; - let isBrushable = events.isBrushable(); - let brush = isBrushable ? events.addBrushEvent(svg) : undefined; - let hover = events.addHoverEvent(); - let mouseout = events.addMouseoutEvent(); - let click = events.addClickEvent(); - let attachedEvents = element.call(hover).call(mouseout).call(click); - - if (isBrushable) { - attachedEvents.call(brush); - } - - return attachedEvents; - }; - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the vertical bar chart - */ - ColumnChart.prototype.draw = function () { - let self = this; - let $elem = $(this.chartEl); - let margin = this._attr.margin; - let elWidth = this._attr.width = $elem.width(); - let elHeight = this._attr.height = $elem.height(); - let yScale = this.handler.yAxis.yScale; - let xScale = this.handler.xAxis.xScale; - let minWidth = 20; - let minHeight = 20; - let addTimeMarker = this._attr.addTimeMarker; - let times = this._attr.times || []; - let timeMarker; - let div; - let svg; - let width; - let height; - let layers; - let bars; - - return function (selection) { - selection.each(function (data) { - layers = self.stackData(data); - - width = elWidth; - height = elHeight - margin.top - margin.bottom; - if (width < minWidth || height < minHeight) { - throw new errors.ContainerTooSmall(); - } - self.validateDataCompliesWithScalingMethod(data); - - if (addTimeMarker) { - timeMarker = new TimeMarker(times, xScale, height); - } + return attachedEvents; + }; - if ( - data.series.length > 1 && - (self._attr.scale === 'log' || self._attr.scale === 'square root') && - (self._attr.mode === 'stacked' || self._attr.mode === 'percentage') - ) { - throw new errors.StackedBarChartConfig(`Cannot display ${self._attr.mode} bar charts for multiple data series \ + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the vertical bar chart + */ + draw() { + const self = this; + const $elem = $(this.chartEl); + const margin = this._attr.margin; + const elWidth = this._attr.width = $elem.width(); + const elHeight = this._attr.height = $elem.height(); + const yScale = this.handler.yAxis.yScale; + const xScale = this.handler.xAxis.xScale; + const minWidth = 20; + const minHeight = 20; + const addTimeMarker = this._attr.addTimeMarker; + const times = this._attr.times || []; + let timeMarker; + + return function (selection) { + selection.each(function (data) { + const layers = self.stackData(data); + + const width = elWidth; + const height = elHeight - margin.top - margin.bottom; + if (width < minWidth || height < minHeight) { + throw new errors.ContainerTooSmall(); + } + self.validateDataCompliesWithScalingMethod(data); + + if (addTimeMarker) { + timeMarker = new TimeMarker(times, xScale, height); + } + + if ( + data.series.length > 1 && + (self._attr.scale === 'log' || self._attr.scale === 'square root') && + (self._attr.mode === 'stacked' || self._attr.mode === 'percentage') + ) { + throw new errors.StackedBarChartConfig(`Cannot display ${self._attr.mode} bar charts for multiple data series \ with a ${self._attr.scale} scaling method. Try 'linear' scaling instead.`); - } - - div = d3.select(this); - - svg = div.append('svg') - .attr('width', width) - .attr('height', height + margin.top + margin.bottom) - .append('g') - .attr('transform', 'translate(0,' + margin.top + ')'); - - bars = self.addBars(svg, layers); - self.createEndZones(svg); - - // Adds event listeners - self.addBarEvents(bars, svg); - - let line = svg.append('line') - .attr('class', 'base-line') - .attr('x1', 0) - .attr('y1', yScale(0)) - .attr('x2', width) - .attr('y2', yScale(0)) - .style('stroke', '#ddd') - .style('stroke-width', 1); - - if (addTimeMarker) { - timeMarker.render(svg); - } - - return svg; - }); + } + + const div = d3.select(this); + + const svg = div.append('svg') + .attr('width', width) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(0,' + margin.top + ')'); + + const bars = self.addBars(svg, layers); + self.createEndZones(svg); + + // Adds event listeners + self.addBarEvents(bars, svg); + + svg.append('line') + .attr('class', 'base-line') + .attr('x1', 0) + .attr('y1', yScale(0)) + .attr('x2', width) + .attr('y2', yScale(0)) + .style('stroke', '#ddd') + .style('stroke-width', 1); + + if (addTimeMarker) { + timeMarker.render(svg); + } + + return svg; + }); + }; }; - }; + } return ColumnChart; }; diff --git a/src/ui/public/vislib/visualizations/line_chart.js b/src/ui/public/vislib/visualizations/line_chart.js index 66b1c035a460b..867a503056a37 100644 --- a/src/ui/public/vislib/visualizations/line_chart.js +++ b/src/ui/public/vislib/visualizations/line_chart.js @@ -6,8 +6,8 @@ import VislibVisualizationsPointSeriesChartProvider from 'ui/vislib/visualizatio import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; export default function LineChartFactory(Private) { - let PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); - let TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); + const PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); + const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); /** * Line Chart Visualization @@ -19,135 +19,136 @@ export default function LineChartFactory(Private) { * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ - _.class(LineChart).inherits(PointSeriesChart); - function LineChart(handler, chartEl, chartData) { - if (!(this instanceof LineChart)) { - return new LineChart(handler, chartEl, chartData); + class LineChart extends PointSeriesChart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); + + // Line chart specific attributes + this._attr = _.defaults(handler._attr || {}, { + interpolate: 'linear', + xValue: function (d) { + return d.x; + }, + yValue: function (d) { + return d.y; + } + }); } - LineChart.Super.apply(this, arguments); + /** + * Adds Events to SVG circle + * + * @method addCircleEvents + * @param element{D3.UpdateSelection} Reference to SVG circle + * @returns {D3.Selection} SVG circles with event listeners attached + */ + addCircleEvents(element, svg) { + const events = this.events; + const isBrushable = events.isBrushable(); + const brush = isBrushable ? events.addBrushEvent(svg) : undefined; + const hover = events.addHoverEvent(); + const mouseout = events.addMouseoutEvent(); + const click = events.addClickEvent(); + const attachedEvents = element.call(hover).call(mouseout).call(click); + + if (isBrushable) { + attachedEvents.call(brush); + } - // Line chart specific attributes - this._attr = _.defaults(handler._attr || {}, { - interpolate: 'linear', - xValue: function (d) { return d.x; }, - yValue: function (d) { return d.y; } - }); - } + return attachedEvents; + }; - /** - * Adds Events to SVG circle - * - * @method addCircleEvents - * @param element{D3.UpdateSelection} Reference to SVG circle - * @returns {D3.Selection} SVG circles with event listeners attached - */ - LineChart.prototype.addCircleEvents = function (element, svg) { - let events = this.events; - let isBrushable = events.isBrushable(); - let brush = isBrushable ? events.addBrushEvent(svg) : undefined; - let hover = events.addHoverEvent(); - let mouseout = events.addMouseoutEvent(); - let click = events.addClickEvent(); - let attachedEvents = element.call(hover).call(mouseout).call(click); - - if (isBrushable) { - attachedEvents.call(brush); - } + /** + * Adds circles to SVG + * + * @method addCircles + * @param svg {HTMLElement} SVG to which rect are appended + * @param data {Array} Array of object data points + * @returns {D3.UpdateSelection} SVG with circles added + */ + addCircles(svg, data) { + const self = this; + const showCircles = this._attr.showCircles; + const color = this.handler.data.getColorFunc(); + const xScale = this.handler.xAxis.xScale; + const yScale = this.handler.yAxis.yScale; + const ordered = this.handler.data.get('ordered'); + const tooltip = this.tooltip; + const isTooltip = this._attr.addTooltip; + + const radii = _(data) + .map(function (series) { + return _.pluck(series, '_input.z'); + }) + .flattenDeep() + .reduce(function (result, val) { + if (result.min > val) result.min = val; + if (result.max < val) result.max = val; + return result; + }, { + min: Infinity, + max: -Infinity + }); - return attachedEvents; - }; + const radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this._attr.radiusRatio, 2); - /** - * Adds circles to SVG - * - * @method addCircles - * @param svg {HTMLElement} SVG to which rect are appended - * @param data {Array} Array of object data points - * @returns {D3.UpdateSelection} SVG with circles added - */ - LineChart.prototype.addCircles = function (svg, data) { - let self = this; - let showCircles = this._attr.showCircles; - let color = this.handler.data.getColorFunc(); - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.yAxis.yScale; - let ordered = this.handler.data.get('ordered'); - let tooltip = this.tooltip; - let isTooltip = this._attr.addTooltip; - - let radii = _(data) - .map(function (series) { - return _.pluck(series, '_input.z'); - }) - .flattenDeep() - .reduce(function (result, val) { - if (result.min > val) result.min = val; - if (result.max < val) result.max = val; - return result; - }, { - min: Infinity, - max: -Infinity - }); - - let radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this._attr.radiusRatio, 2); - - let layer = svg.selectAll('.points') - .data(data) - .enter() + const layer = svg.selectAll('.points') + .data(data) + .enter() .append('g') .attr('class', 'points line'); - let circles = layer - .selectAll('circle') - .data(function appendData(data) { - return data.filter(function (d) { - return !_.isNull(d.y); + const circles = layer + .selectAll('circle') + .data(function appendData(data) { + return data.filter(function (d) { + return !_.isNull(d.y); + }); }); - }); - circles - .exit() - .remove(); + circles + .exit() + .remove(); - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); + function cx(d) { + if (ordered && ordered.date) { + return xScale(d.x); + } + return xScale(d.x) + xScale.rangeBand() / 2; } - return xScale(d.x) + xScale.rangeBand() / 2; - } - function cy(d) { - return yScale(d.y); - } + function cy(d) { + return yScale(d.y); + } - function cColor(d) { - return color(d.label); - } + function cColor(d) { + return color(d.label); + } - function colorCircle(d) { - let parent = d3.select(this).node().parentNode; - let lengthOfParent = d3.select(parent).data()[0].length; - let isVisible = (lengthOfParent === 1); + function colorCircle(d) { + const parent = d3.select(this).node().parentNode; + const lengthOfParent = d3.select(parent).data()[0].length; + const isVisible = (lengthOfParent === 1); - // If only 1 point exists, show circle - if (!showCircles && !isVisible) return 'none'; - return cColor(d); - } - function getCircleRadiusFn(modifier) { - return function getCircleRadius(d) { - let margin = self._attr.margin; - let width = self._attr.width - margin.left - margin.right; - let height = self._attr.height - margin.top - margin.bottom; - let circleRadius = (d._input.z - radii.min) / radiusStep; - - return _.min([Math.sqrt((circleRadius || 2) + 2), width, height]) + (modifier || 0); - }; - } + // If only 1 point exists, show circle + if (!showCircles && !isVisible) return 'none'; + return cColor(d); + } + + function getCircleRadiusFn(modifier) { + return function getCircleRadius(d) { + const margin = self._attr.margin; + const width = self._attr.width - margin.left - margin.right; + const height = self._attr.height - margin.top - margin.bottom; + const circleRadius = (d._input.z - radii.min) / radiusStep; + + return _.min([Math.sqrt((circleRadius || 2) + 2), width, height]) + (modifier || 0); + }; + } - circles - .enter() + circles + .enter() .append('circle') .attr('r', getCircleRadiusFn()) .attr('fill-opacity', (this._attr.drawLinesBetweenPoints ? 1 : 0.7)) @@ -157,8 +158,8 @@ export default function LineChartFactory(Private) { .call(this._addIdentifier) .attr('fill', colorCircle); - circles - .enter() + circles + .enter() .append('circle') .attr('r', getCircleRadiusFn(10)) .attr('cx', cx) @@ -169,187 +170,180 @@ export default function LineChartFactory(Private) { .attr('stroke', cColor) .attr('stroke-width', 0); - if (isTooltip) { - circles.call(tooltip.render()); - } - - return circles; - }; - - /** - * Adds path to SVG - * - * @method addLines - * @param svg {HTMLElement} SVG to which path are appended - * @param data {Array} Array of object data points - * @returns {D3.UpdateSelection} SVG with paths added - */ - LineChart.prototype.addLines = function (svg, data) { - let self = this; - let xScale = this.handler.xAxis.xScale; - let yScale = this.handler.yAxis.yScale; - let xAxisFormatter = this.handler.data.get('xAxisFormatter'); - let color = this.handler.data.getColorFunc(); - let ordered = this.handler.data.get('ordered'); - let interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; - let line = d3.svg.line() - .defined(function (d) { return !_.isNull(d.y); }) - .interpolate(interpolate) - .x(function x(d) { - if (ordered && ordered.date) { - return xScale(d.x); + if (isTooltip) { + circles.call(tooltip.render()); } - return xScale(d.x) + xScale.rangeBand() / 2; - }) - .y(function y(d) { - return yScale(d.y); - }); - let lines; - - lines = svg - .selectAll('.lines') - .data(data) - .enter() - .append('g') - .attr('class', 'pathgroup lines'); - - lines.append('path') - .call(this._addIdentifier) - .attr('d', function lineD(d) { - return line(d.values); - }) - .attr('fill', 'none') - .attr('stroke', function lineStroke(d) { - return color(d.label); - }) - .attr('stroke-width', 2); - - return lines; - }; - - /** - * Adds SVG clipPath - * - * @method addClipPath - * @param svg {HTMLElement} SVG to which clipPath is appended - * @param width {Number} SVG width - * @param height {Number} SVG height - * @returns {D3.UpdateSelection} SVG with clipPath added - */ - LineChart.prototype.addClipPath = function (svg, width, height) { - let clipPathBuffer = 5; - let startX = 0; - let startY = 0 - clipPathBuffer; - let id = 'chart-area' + _.uniqueId(); - - return svg - .attr('clip-path', 'url(#' + id + ')') - .append('clipPath') - .attr('id', id) - .append('rect') - .attr('x', startX) - .attr('y', startY) - .attr('width', width) - // Adding clipPathBuffer to height so it doesn't - // cutoff the lower part of the chart - .attr('height', height + clipPathBuffer); - }; - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the line chart - */ - LineChart.prototype.draw = function () { - let self = this; - let $elem = $(this.chartEl); - let margin = this._attr.margin; - let elWidth = this._attr.width = $elem.width(); - let elHeight = this._attr.height = $elem.height(); - let scaleType = this.handler.yAxis.getScaleType(); - let yMin = this.handler.yAxis.yMin; - let yScale = this.handler.yAxis.yScale; - let xScale = this.handler.xAxis.xScale; - let minWidth = 20; - let minHeight = 20; - let startLineX = 0; - let lineStrokeWidth = 1; - let addTimeMarker = this._attr.addTimeMarker; - let times = this._attr.times || []; - let timeMarker; - let div; - let svg; - let width; - let height; - let lines; - let circles; - - return function (selection) { - selection.each(function (data) { - let el = this; - - let layers = data.series.map(function mapSeries(d) { - let label = d.label; - return d.values.map(function mapValues(e, i) { - return { - _input: e, - label: label, - x: self._attr.xValue.call(d.values, e, i), - y: self._attr.yValue.call(d.values, e, i) - }; - }); - }); - - width = elWidth - margin.left - margin.right; - height = elHeight - margin.top - margin.bottom; - if (width < minWidth || height < minHeight) { - throw new errors.ContainerTooSmall(); - } - self.validateDataCompliesWithScalingMethod(data); + return circles; + }; - if (addTimeMarker) { - timeMarker = new TimeMarker(times, xScale, height); + /** + * Adds path to SVG + * + * @method addLines + * @param svg {HTMLElement} SVG to which path are appended + * @param data {Array} Array of object data points + * @returns {D3.UpdateSelection} SVG with paths added + */ + addLines(svg, data) { + const xScale = this.handler.xAxis.xScale; + const yScale = this.handler.yAxis.yScale; + const xAxisFormatter = this.handler.data.get('xAxisFormatter'); + const color = this.handler.data.getColorFunc(); + const ordered = this.handler.data.get('ordered'); + const interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; + const line = d3.svg.line() + .defined(function (d) { + return !_.isNull(d.y); + }) + .interpolate(interpolate) + .x(function x(d) { + if (ordered && ordered.date) { + return xScale(d.x); } + return xScale(d.x) + xScale.rangeBand() / 2; + }) + .y(function y(d) { + return yScale(d.y); + }); - - - div = d3.select(el); - - svg = div.append('svg') - .attr('width', width + margin.left + margin.right) - .attr('height', height + margin.top + margin.bottom) + const lines = svg + .selectAll('.lines') + .data(data) + .enter() .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + .attr('class', 'pathgroup lines'); + + lines.append('path') + .call(this._addIdentifier) + .attr('d', function lineD(d) { + return line(d.values); + }) + .attr('fill', 'none') + .attr('stroke', function lineStroke(d) { + return color(d.label); + }) + .attr('stroke-width', 2); + + return lines; + }; - self.addClipPath(svg, width, height); - if (self._attr.drawLinesBetweenPoints) { - lines = self.addLines(svg, data.series); - } - circles = self.addCircles(svg, layers); - self.addCircleEvents(circles, svg); - self.createEndZones(svg); - - let scale = (scaleType === 'log') ? yScale(1) : yScale(0); - if (scale) { - svg.append('line') - .attr('class', 'base-line') - .attr('x1', startLineX) - .attr('y1', scale) - .attr('x2', width) - .attr('y2', scale) - .style('stroke', '#ddd') - .style('stroke-width', lineStrokeWidth); - } + /** + * Adds SVG clipPath + * + * @method addClipPath + * @param svg {HTMLElement} SVG to which clipPath is appended + * @param width {Number} SVG width + * @param height {Number} SVG height + * @returns {D3.UpdateSelection} SVG with clipPath added + */ + addClipPath(svg, width, height) { + const clipPathBuffer = 5; + const startX = 0; + const startY = 0 - clipPathBuffer; + const id = 'chart-area' + _.uniqueId(); + + return svg + .attr('clip-path', 'url(#' + id + ')') + .append('clipPath') + .attr('id', id) + .append('rect') + .attr('x', startX) + .attr('y', startY) + .attr('width', width) + // Adding clipPathBuffer to height so it doesn't + // cutoff the lower part of the chart + .attr('height', height + clipPathBuffer); + }; - if (addTimeMarker) { - timeMarker.render(svg); - } + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the line chart + */ + draw() { + const self = this; + const $elem = $(this.chartEl); + const margin = this._attr.margin; + const elWidth = this._attr.width = $elem.width(); + const elHeight = this._attr.height = $elem.height(); + const scaleType = this.handler.yAxis.getScaleType(); + const yScale = this.handler.yAxis.yScale; + const xScale = this.handler.xAxis.xScale; + const minWidth = 20; + const minHeight = 20; + const startLineX = 0; + const lineStrokeWidth = 1; + const addTimeMarker = this._attr.addTimeMarker; + const times = this._attr.times || []; + let timeMarker; + + return function (selection) { + selection.each(function (data) { + const el = this; + + const layers = data.series.map(function mapSeries(d) { + const label = d.label; + return d.values.map(function mapValues(e, i) { + return { + _input: e, + label: label, + x: self._attr.xValue.call(d.values, e, i), + y: self._attr.yValue.call(d.values, e, i) + }; + }); + }); - return svg; - }); + const width = elWidth - margin.left - margin.right; + const height = elHeight - margin.top - margin.bottom; + if (width < minWidth || height < minHeight) { + throw new errors.ContainerTooSmall(); + } + self.validateDataCompliesWithScalingMethod(data); + + if (addTimeMarker) { + timeMarker = new TimeMarker(times, xScale, height); + } + + + const div = d3.select(el); + + const svg = div.append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + self.addClipPath(svg, width, height); + if (self._attr.drawLinesBetweenPoints) { + self.addLines(svg, data.series); + } + const circles = self.addCircles(svg, layers); + self.addCircleEvents(circles, svg); + self.createEndZones(svg); + + const scale = (scaleType === 'log') ? yScale(1) : yScale(0); + if (scale) { + svg.append('line') + .attr('class', 'base-line') + .attr('x1', startLineX) + .attr('y1', scale) + .attr('x2', width) + .attr('y2', scale) + .style('stroke', '#ddd') + .style('stroke-width', lineStrokeWidth); + } + + if (addTimeMarker) { + timeMarker.render(svg); + } + + return svg; + }); + }; }; - }; + } return LineChart; }; diff --git a/src/ui/public/vislib/visualizations/marker_types/base_marker.js b/src/ui/public/vislib/visualizations/marker_types/base_marker.js index 9164e2eb94d00..53801942d9a1f 100644 --- a/src/ui/public/vislib/visualizations/marker_types/base_marker.js +++ b/src/ui/public/vislib/visualizations/marker_types/base_marker.js @@ -11,254 +11,256 @@ export default function MarkerFactory() { * @param geoJson {geoJson Object} * @param params {Object} */ - function BaseMarker(map, geoJson, params) { - this.map = map; - this.geoJson = geoJson; - this.popups = []; - - this._tooltipFormatter = params.tooltipFormatter || _.identity; - this._valueFormatter = params.valueFormatter || _.identity; - this._attr = params.attr || {}; - - // set up the default legend colors - this.quantizeLegendColors(); - } - - /** - * Adds legend div to each map when data is split - * uses d3 scale from BaseMarker.prototype.quantizeLegendColors - * - * @method addLegend - * @return {undefined} - */ - BaseMarker.prototype.addLegend = function () { - // ensure we only ever create 1 legend - if (this._legend) return; - - let self = this; - - // create the legend control, keep a reference - self._legend = L.control({position: 'bottomright'}); - - self._legend.onAdd = function () { - // creates all the neccessary DOM elements for the control, adds listeners - // on relevant map events, and returns the element containing the control - let $div = $('
').addClass('tilemap-legend'); - - _.each(self._legendColors, function (color, i) { - let labelText = self._legendQuantizer - .invertExtent(color) - .map(self._valueFormatter) - .join(' – '); - - let label = $('
').text(labelText); + class BaseMarker { + constructor(map, geoJson, params) { + this.map = map; + this.geoJson = geoJson; + this.popups = []; + + this._tooltipFormatter = params.tooltipFormatter || _.identity; + this._valueFormatter = params.valueFormatter || _.identity; + this._attr = params.attr || {}; + + // set up the default legend colors + this.quantizeLegendColors(); + } - let icon = $('').css({ - background: color, - 'border-color': self.darkerColor(color) + /** + * Adds legend div to each map when data is split + * uses d3 scale from BaseMarker.prototype.quantizeLegendColors + * + * @method addLegend + * @return {undefined} + */ + addLegend() { + // ensure we only ever create 1 legend + if (this._legend) return; + + const self = this; + + // create the legend control, keep a reference + self._legend = L.control({position: 'bottomright'}); + + self._legend.onAdd = function () { + // creates all the neccessary DOM elements for the control, adds listeners + // on relevant map events, and returns the element containing the control + const $div = $('
').addClass('tilemap-legend'); + + _.each(self._legendColors, function (color, i) { + const labelText = self._legendQuantizer + .invertExtent(color) + .map(self._valueFormatter) + .join(' – '); + + const label = $('
').text(labelText); + + const icon = $('').css({ + background: color, + 'border-color': self.darkerColor(color) + }); + + label.append(icon); + $div.append(label); }); - label.append(icon); - $div.append(label); - }); + return $div.get(0); + }; - return $div.get(0); + self._legend.addTo(self.map); }; - self._legend.addTo(self.map); - }; - - /** - * Apply style with shading to feature - * - * @method applyShadingStyle - * @param value {Object} - * @return {Object} - */ - BaseMarker.prototype.applyShadingStyle = function (value) { - let color = this._legendQuantizer(value); - - return { - fillColor: color, - color: this.darkerColor(color), - weight: 1.5, - opacity: 1, - fillOpacity: 0.75 + /** + * Apply style with shading to feature + * + * @method applyShadingStyle + * @param value {Object} + * @return {Object} + */ + applyShadingStyle(value) { + const color = this._legendQuantizer(value); + + return { + fillColor: color, + color: this.darkerColor(color), + weight: 1.5, + opacity: 1, + fillOpacity: 0.75 + }; }; - }; - /** - * Binds popup and events to each feature on map - * - * @method bindPopup - * @param feature {Object} - * @param layer {Object} - * return {undefined} - */ - BaseMarker.prototype.bindPopup = function (feature, layer) { - let self = this; - - let popup = layer.on({ - mouseover: function (e) { - let layer = e.target; - // bring layer to front if not older browser - if (!L.Browser.ie && !L.Browser.opera) { - layer.bringToFront(); + /** + * Binds popup and events to each feature on map + * + * @method bindPopup + * @param feature {Object} + * @param layer {Object} + * return {undefined} + */ + bindPopup(feature, layer) { + const self = this; + + const popup = layer.on({ + mouseover: function (e) { + const layer = e.target; + // bring layer to front if not older browser + if (!L.Browser.ie && !L.Browser.opera) { + layer.bringToFront(); + } + self._showTooltip(feature); + }, + mouseout: function () { + self._hidePopup(); } - self._showTooltip(feature); - }, - mouseout: function (e) { - self._hidePopup(); - } - }); + }); - self.popups.push(popup); - }; + self.popups.push(popup); + }; - /** - * d3 method returns a darker hex color, - * used for marker stroke color - * - * @method darkerColor - * @param color {String} hex color - * @param amount? {Number} amount to darken by - * @return {String} hex color - */ - BaseMarker.prototype.darkerColor = function (color, amount) { - amount = amount || 1.3; - return d3.hcl(color).darker(amount).toString(); - }; - - BaseMarker.prototype.destroy = function () { - let self = this; - - // remove popups - self.popups = self.popups.filter(function (popup) { - popup.off('mouseover').off('mouseout'); - }); - - if (self._legend) { - self.map.removeControl(self._legend); - self._legend = undefined; - } + /** + * d3 method returns a darker hex color, + * used for marker stroke color + * + * @method darkerColor + * @param color {String} hex color + * @param amount? {Number} amount to darken by + * @return {String} hex color + */ + darkerColor(color, amount) { + amount = amount || 1.3; + return d3.hcl(color).darker(amount).toString(); + }; - // remove marker layer from map - if (self._markerGroup) { - self.map.removeLayer(self._markerGroup); - self._markerGroup = undefined; - } - }; + destroy() { + const self = this; - BaseMarker.prototype._addToMap = function () { - this.map.addLayer(this._markerGroup); - }; + // remove popups + self.popups = self.popups.filter(function (popup) { + popup.off('mouseover').off('mouseout'); + }); - /** - * Creates leaflet marker group, passing options to L.geoJson - * - * @method _createMarkerGroup - * @param options {Object} Options to pass to L.geoJson - */ - BaseMarker.prototype._createMarkerGroup = function (options) { - let self = this; - let defaultOptions = { - onEachFeature: function (feature, layer) { - self.bindPopup(feature, layer); - }, - style: function (feature) { - let value = _.get(feature, 'properties.value'); - return self.applyShadingStyle(value); - }, - filter: self._filterToMapBounds() - }; + if (self._legend) { + self.map.removeControl(self._legend); + self._legend = undefined; + } - this._markerGroup = L.geoJson(this.geoJson, _.defaults(defaultOptions, options)); - this._addToMap(); - }; + // remove marker layer from map + if (self._markerGroup) { + self.map.removeLayer(self._markerGroup); + self._markerGroup = undefined; + } + }; - /** - * return whether feature is within map bounds - * - * @method _filterToMapBounds - * @param map {Leaflet Object} - * @return {boolean} - */ - BaseMarker.prototype._filterToMapBounds = function () { - let self = this; - return function (feature) { - let mapBounds = self.map.getBounds(); - let bucketRectBounds = _.get(feature, 'properties.rectangle'); - return mapBounds.intersects(bucketRectBounds); + _addToMap() { + this.map.addLayer(this._markerGroup); }; - }; - /** - * Checks if event latlng is within bounds of mapData - * features and shows tooltip for that feature - * - * @method _showTooltip - * @param feature {LeafletFeature} - * @param latLng? {Leaflet latLng} - * @return undefined - */ - BaseMarker.prototype._showTooltip = function (feature, latLng) { - if (!this.map) return; - let lat = _.get(feature, 'geometry.coordinates.1'); - let lng = _.get(feature, 'geometry.coordinates.0'); - latLng = latLng || L.latLng(lat, lng); + /** + * Creates leaflet marker group, passing options to L.geoJson + * + * @method _createMarkerGroup + * @param options {Object} Options to pass to L.geoJson + */ + _createMarkerGroup(options) { + const self = this; + const defaultOptions = { + onEachFeature: function (feature, layer) { + self.bindPopup(feature, layer); + }, + style: function (feature) { + const value = _.get(feature, 'properties.value'); + return self.applyShadingStyle(value); + }, + filter: self._filterToMapBounds() + }; + + this._markerGroup = L.geoJson(this.geoJson, _.defaults(defaultOptions, options)); + this._addToMap(); + }; - let content = this._tooltipFormatter(feature); + /** + * return whether feature is within map bounds + * + * @method _filterToMapBounds + * @param map {Leaflet Object} + * @return {boolean} + */ + _filterToMapBounds() { + const self = this; + return function (feature) { + const mapBounds = self.map.getBounds(); + const bucketRectBounds = _.get(feature, 'properties.rectangle'); + return mapBounds.intersects(bucketRectBounds); + }; + }; - if (!content) return; - this._createTooltip(content, latLng); - }; + /** + * Checks if event latlng is within bounds of mapData + * features and shows tooltip for that feature + * + * @method _showTooltip + * @param feature {LeafletFeature} + * @param latLng? {Leaflet latLng} + * @return undefined + */ + _showTooltip(feature, latLng) { + if (!this.map) return; + const lat = _.get(feature, 'geometry.coordinates.1'); + const lng = _.get(feature, 'geometry.coordinates.0'); + latLng = latLng || L.latLng(lat, lng); + + const content = this._tooltipFormatter(feature); + + if (!content) return; + this._createTooltip(content, latLng); + }; - BaseMarker.prototype._createTooltip = function (content, latLng) { - L.popup({autoPan: false}) - .setLatLng(latLng) - .setContent(content) - .openOn(this.map); - }; + _createTooltip(content, latLng) { + L.popup({autoPan: false}) + .setLatLng(latLng) + .setContent(content) + .openOn(this.map); + }; - /** - * Closes the tooltip on the map - * - * @method _hidePopup - * @return undefined - */ - BaseMarker.prototype._hidePopup = function () { - if (!this.map) return; + /** + * Closes the tooltip on the map + * + * @method _hidePopup + * @return undefined + */ + _hidePopup() { + if (!this.map) return; - this.map.closePopup(); - }; + this.map.closePopup(); + }; - /** - * d3 quantize scale returns a hex color, used for marker fill color - * - * @method quantizeLegendColors - * return {undefined} - */ - BaseMarker.prototype.quantizeLegendColors = function () { - let min = _.get(this.geoJson, 'properties.allmin', 0); - let max = _.get(this.geoJson, 'properties.allmax', 1); - let quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain(); - - let reds1 = ['#ff6128']; - let reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c']; - let reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; - let bottomCutoff = 2; - let middleCutoff = 24; - - if (max - min <= bottomCutoff) { - this._legendColors = reds1; - } else if (max - min <= middleCutoff) { - this._legendColors = reds3; - } else { - this._legendColors = reds5; - } + /** + * d3 quantize scale returns a hex color, used for marker fill color + * + * @method quantizeLegendColors + * return {undefined} + */ + quantizeLegendColors() { + const min = _.get(this.geoJson, 'properties.allmin', 0); + const max = _.get(this.geoJson, 'properties.allmax', 1); + const quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain(); + + const reds1 = ['#ff6128']; + const reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c']; + const reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; + const bottomCutoff = 2; + const middleCutoff = 24; + + if (max - min <= bottomCutoff) { + this._legendColors = reds1; + } else if (max - min <= middleCutoff) { + this._legendColors = reds3; + } else { + this._legendColors = reds5; + } - this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors); - }; + this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors); + }; + } return BaseMarker; }; diff --git a/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js b/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js index 2dd57ec9fc035..824160fa78471 100644 --- a/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js +++ b/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js @@ -1,9 +1,8 @@ -import _ from 'lodash'; import L from 'leaflet'; import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; export default function GeohashGridMarkerFactory(Private) { - let BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); + const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); /** * Map overlay: rectangles that show the geohash grid bounds @@ -12,27 +11,23 @@ export default function GeohashGridMarkerFactory(Private) { * @param geoJson {geoJson Object} * @param params {Object} */ - _.class(GeohashGridMarker).inherits(BaseMarker); - function GeohashGridMarker(map, geoJson, params) { - let self = this; - GeohashGridMarker.Super.apply(this, arguments); + class GeohashGridMarker extends BaseMarker { + constructor(map, geoJson, params) { + super(map, geoJson, params); - // super min and max from all chart data - let min = this.geoJson.properties.allmin; - let max = this.geoJson.properties.allmax; - - this._createMarkerGroup({ - pointToLayer: function (feature, latlng) { - let geohashRect = feature.properties.rectangle; - // get bounds from northEast[3] and southWest[1] - // corners in geohash rectangle - let corners = [ - [geohashRect[3][0], geohashRect[3][1]], - [geohashRect[1][0], geohashRect[1][1]] - ]; - return L.rectangle(corners); - } - }); + this._createMarkerGroup({ + pointToLayer: function (feature, latlng) { + const geohashRect = feature.properties.rectangle; + // get bounds from northEast[3] and southWest[1] + // corners in geohash rectangle + const corners = [ + [geohashRect[3][0], geohashRect[3][1]], + [geohashRect[1][0], geohashRect[1][1]] + ]; + return L.rectangle(corners); + } + }); + } } return GeohashGridMarker; diff --git a/src/ui/public/vislib/visualizations/marker_types/heatmap.js b/src/ui/public/vislib/visualizations/marker_types/heatmap.js index a066295c4eb29..d5878dec75d7a 100644 --- a/src/ui/public/vislib/visualizations/marker_types/heatmap.js +++ b/src/ui/public/vislib/visualizations/marker_types/heatmap.js @@ -4,7 +4,7 @@ import L from 'leaflet'; import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; export default function HeatmapMarkerFactory(Private) { - let BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); + const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); /** * Map overlay: canvas layer with leaflet.heat plugin @@ -13,198 +13,185 @@ export default function HeatmapMarkerFactory(Private) { * @param geoJson {geoJson Object} * @param params {Object} */ - _.class(HeatmapMarker).inherits(BaseMarker); - function HeatmapMarker(map, geoJson, params) { - let self = this; - this._disableTooltips = false; - HeatmapMarker.Super.apply(this, arguments); - - this._createMarkerGroup({ - radius: +this._attr.heatRadius, - blur: +this._attr.heatBlur, - maxZoom: +this._attr.heatMaxZoom, - minOpacity: +this._attr.heatMinOpacity - }); - } - - /** - * Does nothing, heatmaps don't have a legend - * - * @method addLegend - * @return {undefined} - */ - HeatmapMarker.prototype.addLegend = _.noop; - - HeatmapMarker.prototype._createMarkerGroup = function (options) { - let max = _.get(this.geoJson, 'properties.allmax'); - let points = this._dataToHeatArray(max); - - this._markerGroup = L.heatLayer(points, options); - this._fixTooltips(); - this._addToMap(); - }; - - HeatmapMarker.prototype._fixTooltips = function () { - let self = this; - let debouncedMouseMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, { - 'leading': true, - 'trailing': false - }); - - if (!this._disableTooltips && this._attr.addTooltip) { - this.map.on('mousemove', debouncedMouseMoveLocation); - this.map.on('mouseout', function () { - self.map.closePopup(); + class HeatmapMarker extends BaseMarker { + constructor(map, geoJson, params) { + super(map, geoJson, params); + this._disableTooltips = false; + + this._createMarkerGroup({ + radius: +this._attr.heatRadius, + blur: +this._attr.heatBlur, + maxZoom: +this._attr.heatMaxZoom, + minOpacity: +this._attr.heatMinOpacity }); - this.map.on('mousedown', function () { - self._disableTooltips = true; - self.map.closePopup(); - }); - this.map.on('mouseup', function () { - self._disableTooltips = false; + + this.addLegend = _.noop; + + this._getLatLng = _.memoize(function (feature) { + return L.latLng( + feature.geometry.coordinates[1], + feature.geometry.coordinates[0] + ); + }, function (feature) { + // turn coords into a string for the memoize cache + return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(','); }); } - function mouseMoveLocation(e) { - let latlng = e.latlng; + _createMarkerGroup(options) { + const max = _.get(this.geoJson, 'properties.allmax'); + const points = this._dataToHeatArray(max); - this.map.closePopup(); + this._markerGroup = L.heatLayer(points, options); + this._fixTooltips(); + this._addToMap(); + }; - // unhighlight all svgs - d3.selectAll('path.geohash', this.chartEl).classed('geohash-hover', false); + _fixTooltips() { + const self = this; + const debouncedMouseMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, { + 'leading': true, + 'trailing': false + }); - if (!this.geoJson.features.length || this._disableTooltips) { - return; + if (!this._disableTooltips && this._attr.addTooltip) { + this.map.on('mousemove', debouncedMouseMoveLocation); + this.map.on('mouseout', function () { + self.map.closePopup(); + }); + this.map.on('mousedown', function () { + self._disableTooltips = true; + self.map.closePopup(); + }); + this.map.on('mouseup', function () { + self._disableTooltips = false; + }); } - // find nearest feature to event latlng - let feature = this._nearestFeature(latlng); + function mouseMoveLocation(e) { + const latlng = e.latlng; - // show tooltip if close enough to event latlng - if (this._tooltipProximity(latlng, feature)) { - this._showTooltip(feature, latlng); - } - } - }; + this.map.closePopup(); - /** - * returns a memoized Leaflet latLng for given geoJson feature - * - * @method addLatLng - * @param feature {geoJson Object} - * @return {Leaflet latLng Object} - */ - HeatmapMarker.prototype._getLatLng = _.memoize(function (feature) { - return L.latLng( - feature.geometry.coordinates[1], - feature.geometry.coordinates[0] - ); - }, function (feature) { - // turn coords into a string for the memoize cache - return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(','); - }); + // unhighlight all svgs + d3.selectAll('path.geohash', this.chartEl).classed('geohash-hover', false); - /** - * Finds nearest feature in mapData to event latlng - * - * @method _nearestFeature - * @param latLng {Leaflet latLng} - * @return nearestPoint {Leaflet latLng} - */ - HeatmapMarker.prototype._nearestFeature = function (latLng) { - let self = this; - let nearest; + if (!this.geoJson.features.length || this._disableTooltips) { + return; + } - if (latLng.lng < -180 || latLng.lng > 180) { - return; - } + // find nearest feature to event latlng + const feature = this._nearestFeature(latlng); - _.reduce(this.geoJson.features, function (distance, feature) { - let featureLatLng = self._getLatLng(feature); - let dist = latLng.distanceTo(featureLatLng); - - if (dist < distance) { - nearest = feature; - return dist; + // show tooltip if close enough to event latlng + if (this._tooltipProximity(latlng, feature)) { + this._showTooltip(feature, latlng); + } + } + }; + + /** + * Finds nearest feature in mapData to event latlng + * + * @method _nearestFeature + * @param latLng {Leaflet latLng} + * @return nearestPoint {Leaflet latLng} + */ + _nearestFeature(latLng) { + const self = this; + let nearest; + + if (latLng.lng < -180 || latLng.lng > 180) { + return; } - return distance; - }, Infinity); - - return nearest; - }; - - /** - * display tooltip if feature is close enough to event latlng - * - * @method _tooltipProximity - * @param latlng {Leaflet latLng Object} - * @param feature {geoJson Object} - * @return {Boolean} - */ - HeatmapMarker.prototype._tooltipProximity = function (latlng, feature) { - if (!feature) return; - - let showTip = false; - let featureLatLng = this._getLatLng(feature); - - // zoomScale takes map zoom and returns proximity value for tooltip display - // domain (input values) is map zoom (min 1 and max 18) - // range (output values) is distance in meters - // used to compare proximity of event latlng to feature latlng - let zoomScale = d3.scale.linear() - .domain([1, 4, 7, 10, 13, 16, 18]) - .range([1000000, 300000, 100000, 15000, 2000, 150, 50]); - - let proximity = zoomScale(this.map.getZoom()); - let distance = latlng.distanceTo(featureLatLng); - - // maxLngDif is max difference in longitudes - // to prevent feature tooltip from appearing 360° - // away from event latlng - let maxLngDif = 40; - let lngDif = Math.abs(latlng.lng - featureLatLng.lng); - - if (distance < proximity && lngDif < maxLngDif) { - showTip = true; - } - - let testScale = d3.scale.pow().exponent(0.2) - .domain([1, 18]) - .range([1500000, 50]); - return showTip; - }; - - - /** - * returns data for data for heat map intensity - * if heatNormalizeData attribute is checked/true - • normalizes data for heat map intensity - * - * @method _dataToHeatArray - * @param max {Number} - * @return {Array} - */ - HeatmapMarker.prototype._dataToHeatArray = function (max) { - let self = this; - let mapData = this.geoJson; - - return this.geoJson.features.map(function (feature) { - let lat = feature.properties.center[0]; - let lng = feature.properties.center[1]; - let heatIntensity; - - if (!self._attr.heatNormalizeData) { - // show bucket value on heatmap - heatIntensity = feature.properties.value; - } else { - // show bucket value normalized to max value - heatIntensity = feature.properties.value / max; + _.reduce(this.geoJson.features, function (distance, feature) { + const featureLatLng = self._getLatLng(feature); + const dist = latLng.distanceTo(featureLatLng); + + if (dist < distance) { + nearest = feature; + return dist; + } + + return distance; + }, Infinity); + + return nearest; + }; + + /** + * display tooltip if feature is close enough to event latlng + * + * @method _tooltipProximity + * @param latlng {Leaflet latLng Object} + * @param feature {geoJson Object} + * @return {Boolean} + */ + _tooltipProximity(latlng, feature) { + if (!feature) return; + + let showTip = false; + const featureLatLng = this._getLatLng(feature); + + // zoomScale takes map zoom and returns proximity value for tooltip display + // domain (input values) is map zoom (min 1 and max 18) + // range (output values) is distance in meters + // used to compare proximity of event latlng to feature latlng + const zoomScale = d3.scale.linear() + .domain([1, 4, 7, 10, 13, 16, 18]) + .range([1000000, 300000, 100000, 15000, 2000, 150, 50]); + + const proximity = zoomScale(this.map.getZoom()); + const distance = latlng.distanceTo(featureLatLng); + + // maxLngDif is max difference in longitudes + // to prevent feature tooltip from appearing 360° + // away from event latlng + const maxLngDif = 40; + const lngDif = Math.abs(latlng.lng - featureLatLng.lng); + + if (distance < proximity && lngDif < maxLngDif) { + showTip = true; } - return [lat, lng, heatIntensity]; - }); - }; + d3.scale.pow().exponent(0.2) + .domain([1, 18]) + .range([1500000, 50]); + return showTip; + }; + + + /** + * returns data for data for heat map intensity + * if heatNormalizeData attribute is checked/true + • normalizes data for heat map intensity + * + * @method _dataToHeatArray + * @param max {Number} + * @return {Array} + */ + _dataToHeatArray(max) { + const self = this; + + return this.geoJson.features.map(function (feature) { + const lat = feature.properties.center[0]; + const lng = feature.properties.center[1]; + let heatIntensity; + + if (!self._attr.heatNormalizeData) { + // show bucket value on heatmap + heatIntensity = feature.properties.value; + } else { + // show bucket value normalized to max value + heatIntensity = feature.properties.value / max; + } + + return [lat, lng, heatIntensity]; + }); + }; + } + return HeatmapMarker; }; diff --git a/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js b/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js index 8a0c84841dc7b..9e6afcbd49ac8 100644 --- a/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js +++ b/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js @@ -3,7 +3,7 @@ import L from 'leaflet'; import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; export default function ScaledCircleMarkerFactory(Private) { - let BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); + const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); /** * Map overlay: circle markers that are scaled to illustrate values @@ -12,48 +12,48 @@ export default function ScaledCircleMarkerFactory(Private) { * @param mapData {geoJson Object} * @param params {Object} */ - _.class(ScaledCircleMarker).inherits(BaseMarker); - function ScaledCircleMarker(map, geoJson, params) { - let self = this; - ScaledCircleMarker.Super.apply(this, arguments); - - // multiplier to reduce size of all circles - let scaleFactor = 0.6; - - this._createMarkerGroup({ - pointToLayer: function (feature, latlng) { - let value = feature.properties.value; - let scaledRadius = self._radiusScale(value) * scaleFactor; - return L.circleMarker(latlng).setRadius(scaledRadius); - } - }); + class ScaledCircleMarker extends BaseMarker { + constructor(map, geoJson, params) { + super(map, geoJson, params); + + // multiplier to reduce size of all circles + const scaleFactor = 0.6; + + this._createMarkerGroup({ + pointToLayer: (feature, latlng) => { + const value = feature.properties.value; + const scaledRadius = this._radiusScale(value) * scaleFactor; + return L.circleMarker(latlng).setRadius(scaledRadius); + } + }); + } + + /** + * radiusScale returns a number for scaled circle markers + * for relative sizing of markers + * + * @method _radiusScale + * @param value {Number} + * @return {Number} + */ + _radiusScale(value) { + const precisionBiasBase = 5; + const precisionBiasNumerator = 200; + const zoom = this.map.getZoom(); + const maxValue = this.geoJson.properties.allmax; + const precision = _.max(this.geoJson.features.map(function (feature) { + return String(feature.properties.geohash).length; + })); + + const pct = Math.abs(value) / Math.abs(maxValue); + const zoomRadius = 0.5 * Math.pow(2, zoom); + const precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision); + + // square root value percentage + return Math.pow(pct, 0.5) * zoomRadius * precisionScale; + }; } - /** - * radiusScale returns a number for scaled circle markers - * for relative sizing of markers - * - * @method _radiusScale - * @param value {Number} - * @return {Number} - */ - ScaledCircleMarker.prototype._radiusScale = function (value) { - let precisionBiasBase = 5; - let precisionBiasNumerator = 200; - let zoom = this.map.getZoom(); - let maxValue = this.geoJson.properties.allmax; - let precision = _.max(this.geoJson.features.map(function (feature) { - return String(feature.properties.geohash).length; - })); - - let pct = Math.abs(value) / Math.abs(maxValue); - let zoomRadius = 0.5 * Math.pow(2, zoom); - let precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision); - - // square root value percentage - return Math.pow(pct, 0.5) * zoomRadius * precisionScale; - }; - return ScaledCircleMarker; }; diff --git a/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js b/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js index c873f2f9c1613..2d31cdc6585d0 100644 --- a/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js +++ b/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js @@ -3,7 +3,7 @@ import L from 'leaflet'; import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; export default function ShadedCircleMarkerFactory(Private) { - let BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); + const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); /** * Map overlay: circle markers that are shaded to illustrate values @@ -12,56 +12,54 @@ export default function ShadedCircleMarkerFactory(Private) { * @param mapData {geoJson Object} * @return {Leaflet object} featureLayer */ - _.class(ShadedCircleMarker).inherits(BaseMarker); - function ShadedCircleMarker(map, geoJson, params) { - let self = this; - ShadedCircleMarker.Super.apply(this, arguments); + class ShadedCircleMarker extends BaseMarker { + constructor(map, geoJson, params) { + super(map, geoJson, params); - // super min and max from all chart data - let min = this.geoJson.properties.allmin; - let max = this.geoJson.properties.allmax; + // multiplier to reduce size of all circles + const scaleFactor = 0.8; - // multiplier to reduce size of all circles - let scaleFactor = 0.8; + this._createMarkerGroup({ + pointToLayer: (feature, latlng) => { + const radius = this._geohashMinDistance(feature) * scaleFactor; + return L.circle(latlng, radius); + } + }); + } - this._createMarkerGroup({ - pointToLayer: function (feature, latlng) { - let radius = self._geohashMinDistance(feature) * scaleFactor; - return L.circle(latlng, radius); - } - }); - } - /** - * _geohashMinDistance returns a min distance in meters for sizing - * circle markers to fit within geohash grid rectangle - * - * @method _geohashMinDistance - * @param feature {Object} - * @return {Number} - */ - ShadedCircleMarker.prototype._geohashMinDistance = function (feature) { - let centerPoint = _.get(feature, 'properties.center'); - let geohashRect = _.get(feature, 'properties.rectangle'); + /** + * _geohashMinDistance returns a min distance in meters for sizing + * circle markers to fit within geohash grid rectangle + * + * @method _geohashMinDistance + * @param feature {Object} + * @return {Number} + */ + _geohashMinDistance(feature) { + const centerPoint = _.get(feature, 'properties.center'); + const geohashRect = _.get(feature, 'properties.rectangle'); + + // centerPoint is an array of [lat, lng] + // geohashRect is the 4 corners of the geoHash rectangle + // an array that starts at the southwest corner and proceeds + // clockwise, each value being an array of [lat, lng] - // centerPoint is an array of [lat, lng] - // geohashRect is the 4 corners of the geoHash rectangle - // an array that starts at the southwest corner and proceeds - // clockwise, each value being an array of [lat, lng] + // center lat and southeast lng + const east = L.latLng([centerPoint[0], geohashRect[2][1]]); + // southwest lat and center lng + const north = L.latLng([geohashRect[3][0], centerPoint[1]]); - // center lat and southeast lng - let east = L.latLng([centerPoint[0], geohashRect[2][1]]); - // southwest lat and center lng - let north = L.latLng([geohashRect[3][0], centerPoint[1]]); + // get latLng of geohash center point + const center = L.latLng([centerPoint[0], centerPoint[1]]); - // get latLng of geohash center point - let center = L.latLng([centerPoint[0], centerPoint[1]]); + // get smallest radius at center of geohash grid rectangle + const eastRadius = Math.floor(center.distanceTo(east)); + const northRadius = Math.floor(center.distanceTo(north)); + return _.min([eastRadius, northRadius]); + }; + } - // get smallest radius at center of geohash grid rectangle - let eastRadius = Math.floor(center.distanceTo(east)); - let northRadius = Math.floor(center.distanceTo(north)); - return _.min([eastRadius, northRadius]); - }; return ShadedCircleMarker; }; diff --git a/src/ui/public/vislib/visualizations/pie_chart.js b/src/ui/public/vislib/visualizations/pie_chart.js index b18c0820942c1..e3ad68584c7be 100644 --- a/src/ui/public/vislib/visualizations/pie_chart.js +++ b/src/ui/public/vislib/visualizations/pie_chart.js @@ -5,7 +5,7 @@ import errors from 'ui/errors'; import VislibVisualizationsChartProvider from 'ui/vislib/visualizations/_chart'; export default function PieChartFactory(Private) { - let Chart = Private(VislibVisualizationsChartProvider); + const Chart = Private(VislibVisualizationsChartProvider); /** * Pie Chart Visualization @@ -17,193 +17,196 @@ export default function PieChartFactory(Private) { * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ - _.class(PieChart).inherits(Chart); - function PieChart(handler, chartEl, chartData) { - if (!(this instanceof PieChart)) { - return new PieChart(handler, chartEl, chartData); - } - PieChart.Super.apply(this, arguments); - - let charts = this.handler.data.getVisData(); - this._validatePieData(charts); + class PieChart extends Chart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); - this._attr = _.defaults(handler._attr || {}, { - isDonut: handler._attr.isDonut || false - }); - } + const charts = this.handler.data.getVisData(); + this._validatePieData(charts); - /** - * Checks whether pie slices have all zero values. - * If so, an error is thrown. - */ - PieChart.prototype._validatePieData = function (charts) { - let isAllZeros = charts.every(function (chart) { - return chart.slices.children.length === 0; - }); + this._attr = _.defaults(handler._attr || {}, { + isDonut: handler._attr.isDonut || false + }); + } - if (isAllZeros) { throw new errors.PieContainsAllZeros(); } - }; + /** + * Checks whether pie slices have all zero values. + * If so, an error is thrown. + */ + _validatePieData(charts) { + const isAllZeros = charts.every(function (chart) { + return chart.slices.children.length === 0; + }); - /** - * Adds Events to SVG paths - * - * @method addPathEvents - * @param element {D3.Selection} Reference to SVG path - * @returns {D3.Selection} SVG path with event listeners attached - */ - PieChart.prototype.addPathEvents = function (element) { - let events = this.events; + if (isAllZeros) { + throw new errors.PieContainsAllZeros(); + } + }; - return element + /** + * Adds Events to SVG paths + * + * @method addPathEvents + * @param element {D3.Selection} Reference to SVG path + * @returns {D3.Selection} SVG path with event listeners attached + */ + addPathEvents(element) { + const events = this.events; + + return element .call(events.addHoverEvent()) .call(events.addMouseoutEvent()) .call(events.addClickEvent()); - }; + }; - PieChart.prototype.convertToPercentage = function (slices) { - (function assignPercentages(slices) { - if (slices.sumOfChildren != null) return; + convertToPercentage(slices) { + (function assignPercentages(slices) { + if (slices.sumOfChildren != null) return; - let parent = slices; - let children = parent.children; - let parentPercent = parent.percentOfParent; + const parent = slices; + const children = parent.children; + const parentPercent = parent.percentOfParent; - let sum = parent.sumOfChildren = Math.abs(children.reduce(function (sum, child) { - return sum + Math.abs(child.size); - }, 0)); + const sum = parent.sumOfChildren = Math.abs(children.reduce(function (sum, child) { + return sum + Math.abs(child.size); + }, 0)); - children.forEach(function (child) { - child.percentOfGroup = Math.abs(child.size) / sum; - child.percentOfParent = child.percentOfGroup; + children.forEach(function (child) { + child.percentOfGroup = Math.abs(child.size) / sum; + child.percentOfParent = child.percentOfGroup; - if (parentPercent != null) { - child.percentOfParent *= parentPercent; - } + if (parentPercent != null) { + child.percentOfParent *= parentPercent; + } - if (child.children) { - assignPercentages(child); - } + if (child.children) { + assignPercentages(child); + } + }); + }(slices)); + }; + + /** + * Adds pie paths to SVG + * + * @method addPath + * @param width {Number} Width of SVG + * @param height {Number} Height of SVG + * @param svg {HTMLElement} Chart SVG + * @param slices {Object} Chart data + * @returns {D3.Selection} SVG with paths attached + */ + addPath(width, height, svg, slices) { + const self = this; + const marginFactor = 0.95; + const isDonut = self._attr.isDonut; + const radius = (Math.min(width, height) / 2) * marginFactor; + const color = self.handler.data.getPieColorFunc(); + const tooltip = self.tooltip; + const isTooltip = self._attr.addTooltip; + + const partition = d3.layout.partition() + .sort(null) + .value(function (d) { + return d.percentOfParent * 100; }); - }(slices)); - }; + const x = d3.scale.linear() + .range([0, 2 * Math.PI]); + const y = d3.scale.sqrt() + .range([0, radius]); + const arc = d3.svg.arc() + .startAngle(function (d) { + return Math.max(0, Math.min(2 * Math.PI, x(d.x))); + }) + .endAngle(function (d) { + return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); + }) + .innerRadius(function (d) { + // option for a single layer, i.e pie chart + if (d.depth === 1 && !isDonut) { + // return no inner radius + return 0; + } - /** - * Adds pie paths to SVG - * - * @method addPath - * @param width {Number} Width of SVG - * @param height {Number} Height of SVG - * @param svg {HTMLElement} Chart SVG - * @param slices {Object} Chart data - * @returns {D3.Selection} SVG with paths attached - */ - PieChart.prototype.addPath = function (width, height, svg, slices) { - let self = this; - let marginFactor = 0.95; - let isDonut = self._attr.isDonut; - let radius = (Math.min(width, height) / 2) * marginFactor; - let color = self.handler.data.getPieColorFunc(); - let tooltip = self.tooltip; - let isTooltip = self._attr.addTooltip; - - let partition = d3.layout.partition() - .sort(null) - .value(function (d) { - return d.percentOfParent * 100; - }); - let x = d3.scale.linear() - .range([0, 2 * Math.PI]); - let y = d3.scale.sqrt() - .range([0, radius]); - let arc = d3.svg.arc() - .startAngle(function (d) { - return Math.max(0, Math.min(2 * Math.PI, x(d.x))); - }) - .endAngle(function (d) { - return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); - }) - .innerRadius(function (d) { - // option for a single layer, i.e pie chart - if (d.depth === 1 && !isDonut) { - // return no inner radius - return 0; - } + return Math.max(0, y(d.y)); + }) + .outerRadius(function (d) { + return Math.max(0, y(d.y + d.dy)); + }); - return Math.max(0, y(d.y)); - }) - .outerRadius(function (d) { - return Math.max(0, y(d.y + d.dy)); - }); - - let path = svg - .datum(slices) - .selectAll('path') - .data(partition.nodes) - .enter() + const path = svg + .datum(slices) + .selectAll('path') + .data(partition.nodes) + .enter() .append('path') .attr('d', arc) .attr('class', function (d) { - if (d.depth === 0) { return; } + if (d.depth === 0) { + return; + } return 'slice'; }) .call(self._addIdentifier, 'name') .style('stroke', '#fff') .style('fill', function (d) { - if (d.depth === 0) { return 'none'; } + if (d.depth === 0) { + return 'none'; + } return color(d.name); }); - if (isTooltip) { - path.call(tooltip.render()); - } - - return path; - }; - - PieChart.prototype._validateContainerSize = function (width, height) { - let minWidth = 20; - let minHeight = 20; - - if (width <= minWidth || height <= minHeight) { - throw new errors.ContainerTooSmall(); - } - }; - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the pie chart - */ - PieChart.prototype.draw = function () { - let self = this; - - return function (selection) { - selection.each(function (data) { - let slices = data.slices; - let div = d3.select(this); - let width = $(this).width(); - let height = $(this).height(); - let path; - - if (!slices.children.length) return; + if (isTooltip) { + path.call(tooltip.render()); + } - self.convertToPercentage(slices); - self._validateContainerSize(width, height); + return path; + }; - let svg = div.append('svg') - .attr('width', width) - .attr('height', height) - .append('g') - .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); + _validateContainerSize(width, height) { + const minWidth = 20; + const minHeight = 20; - path = self.addPath(width, height, svg, slices); - self.addPathEvents(path); + if (width <= minWidth || height <= minHeight) { + throw new errors.ContainerTooSmall(); + } + }; - return svg; - }); + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the pie chart + */ + draw() { + const self = this; + + return function (selection) { + selection.each(function (data) { + const slices = data.slices; + const div = d3.select(this); + const width = $(this).width(); + const height = $(this).height(); + + if (!slices.children.length) return; + + self.convertToPercentage(slices); + self._validateContainerSize(width, height); + + const svg = div.append('svg') + .attr('width', width) + .attr('height', height) + .append('g') + .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); + + const path = self.addPath(width, height, svg, slices); + self.addPathEvents(path); + + return svg; + }); + }; }; - }; + } return PieChart; }; diff --git a/src/ui/public/vislib/visualizations/tile_map.js b/src/ui/public/vislib/visualizations/tile_map.js index bc3d3bab83a75..3978cfdaa65e0 100644 --- a/src/ui/public/vislib/visualizations/tile_map.js +++ b/src/ui/public/vislib/visualizations/tile_map.js @@ -5,8 +5,8 @@ import VislibVisualizationsChartProvider from 'ui/vislib/visualizations/_chart'; import VislibVisualizationsMapProvider from 'ui/vislib/visualizations/_map'; export default function TileMapFactory(Private) { - let Chart = Private(VislibVisualizationsChartProvider); - let TileMapMap = Private(VislibVisualizationsMapProvider); + const Chart = Private(VislibVisualizationsChartProvider); + const TileMapMap = Private(VislibVisualizationsMapProvider); /** * Tile Map Visualization: renders maps @@ -18,117 +18,114 @@ export default function TileMapFactory(Private) { * @param chartEl {HTMLElement} HTML element to which the map will be appended * @param chartData {Object} Elasticsearch query results for this map */ - _.class(TileMap).inherits(Chart); - function TileMap(handler, chartEl, chartData) { - if (!(this instanceof TileMap)) { - return new TileMap(handler, chartEl, chartData); - } - - TileMap.Super.apply(this, arguments); + class TileMap extends Chart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); - // track the map objects - this.maps = []; - this._chartData = chartData || {}; - _.assign(this, this._chartData); - - this._appendGeoExtents(); - } + // track the map objects + this.maps = []; + this._chartData = chartData || {}; + _.assign(this, this._chartData); - /** - * Draws tile map, called on chart render - * - * @method draw - * @return {Function} - function to add a map to a selection - */ - TileMap.prototype.draw = function () { - let self = this; + this._appendGeoExtents(); + } - // clean up old maps - self.destroy(); + /** + * Draws tile map, called on chart render + * + * @method draw + * @return {Function} - function to add a map to a selection + */ + draw() { + const self = this; + + // clean up old maps + self.destroy(); + + return function (selection) { + selection.each(function () { + self._appendMap(this); + }); + }; + }; - return function (selection) { - selection.each(function () { - self._appendMap(this); + /** + * Invalidate the size of the map, so that leaflet will resize to fit. + * then moves to center + * + * @method resizeArea + * @return {undefined} + */ + resizeArea() { + this.maps.forEach(function (map) { + map.updateSize(); }); }; - }; - /** - * Invalidate the size of the map, so that leaflet will resize to fit. - * then moves to center - * - * @method resizeArea - * @return {undefined} - */ - TileMap.prototype.resizeArea = function () { - this.maps.forEach(function (map) { - map.updateSize(); - }); - }; + /** + * clean up the maps + * + * @method destroy + * @return {undefined} + */ + destroy() { + this.maps = this.maps.filter(function (map) { + map.destroy(); + }); + }; - /** - * clean up the maps - * - * @method destroy - * @return {undefined} - */ - TileMap.prototype.destroy = function () { - this.maps = this.maps.filter(function (map) { - map.destroy(); - }); - }; + /** + * Adds allmin and allmax properties to geoJson data + * + * @method _appendMap + * @param selection {Object} d3 selection + */ + _appendGeoExtents() { + // add allmin and allmax to geoJson + const geoMinMax = this.handler.data.getGeoExtents(); + this.geoJson.properties.allmin = geoMinMax.min; + this.geoJson.properties.allmax = geoMinMax.max; + }; - /** - * Adds allmin and allmax properties to geoJson data - * - * @method _appendMap - * @param selection {Object} d3 selection - */ - TileMap.prototype._appendGeoExtents = function () { - // add allmin and allmax to geoJson - let geoMinMax = this.handler.data.getGeoExtents(); - this.geoJson.properties.allmin = geoMinMax.min; - this.geoJson.properties.allmax = geoMinMax.max; - }; + /** + * Renders map + * + * @method _appendMap + * @param selection {Object} d3 selection + */ + _appendMap(selection) { + const container = $(selection).addClass('tilemap'); + const uiStateParams = this.handler.vis ? { + mapCenter: this.handler.vis.uiState.get('mapCenter'), + mapZoom: this.handler.vis.uiState.get('mapZoom') + } : {}; + + const params = _.assign({}, _.get(this._chartData, 'geoAgg.vis.params'), uiStateParams); + + const map = new TileMapMap(container, this._chartData, { + center: params.mapCenter, + zoom: params.mapZoom, + events: this.events, + markerType: this._attr.mapType, + tooltipFormatter: this.tooltipFormatter, + valueFormatter: this.valueFormatter, + attr: this._attr + }); - /** - * Renders map - * - * @method _appendMap - * @param selection {Object} d3 selection - */ - TileMap.prototype._appendMap = function (selection) { - const container = $(selection).addClass('tilemap'); - const uiStateParams = this.handler.vis ? { - mapCenter: this.handler.vis.uiState.get('mapCenter'), - mapZoom: this.handler.vis.uiState.get('mapZoom') - } : {}; - - const params = _.assign({}, _.get(this._chartData, 'geoAgg.vis.params'), uiStateParams); - - const map = new TileMapMap(container, this._chartData, { - center: params.mapCenter, - zoom: params.mapZoom, - events: this.events, - markerType: this._attr.mapType, - tooltipFormatter: this.tooltipFormatter, - valueFormatter: this.valueFormatter, - attr: this._attr - }); - - // add title for splits - if (this.title) { - map.addTitle(this.title); - } + // add title for splits + if (this.title) { + map.addTitle(this.title); + } - // add fit to bounds control - if (_.get(this.geoJson, 'features.length') > 0) { - map.addFitControl(); - map.addBoundingControl(); - } + // add fit to bounds control + if (_.get(this.geoJson, 'features.length') > 0) { + map.addFitControl(); + map.addBoundingControl(); + } - this.maps.push(map); - }; + this.maps.push(map); + }; + } return TileMap; }; diff --git a/src/ui/public/vislib/visualizations/time_marker.js b/src/ui/public/vislib/visualizations/time_marker.js index 19fbc2fb9ee52..89e1ad9c06dd5 100644 --- a/src/ui/public/vislib/visualizations/time_marker.js +++ b/src/ui/public/vislib/visualizations/time_marker.js @@ -2,47 +2,44 @@ import d3 from 'd3'; import dateMath from '@elastic/datemath'; export default function TimeMarkerFactory() { - function TimeMarker(times, xScale, height) { - if (!(this instanceof TimeMarker)) { - return new TimeMarker(times, xScale, height); - } - - let currentTimeArr = [{ - 'time': new Date().getTime(), - 'class': 'time-marker', - 'color': '#c80000', - 'opacity': 0.3, - 'width': 2 - }]; + class TimeMarker { + constructor(times, xScale, height) { + const currentTimeArr = [{ + 'time': new Date().getTime(), + 'class': 'time-marker', + 'color': '#c80000', + 'opacity': 0.3, + 'width': 2 + }]; - this.xScale = xScale; - this.height = height; - this.times = (times.length) ? times.map(function (d) { - return { - 'time': dateMath.parse(d.time), - 'class': d.class || 'time-marker', - 'color': d.color || '#c80000', - 'opacity': d.opacity || 0.3, - 'width': d.width || 2 - }; - }) : currentTimeArr; - } + this.xScale = xScale; + this.height = height; + this.times = (times.length) ? times.map(function (d) { + return { + 'time': dateMath.parse(d.time), + 'class': d.class || 'time-marker', + 'color': d.color || '#c80000', + 'opacity': d.opacity || 0.3, + 'width': d.width || 2 + }; + }) : currentTimeArr; + } - TimeMarker.prototype._isTimeBasedChart = function (selection) { - let data = selection.data(); - return data.every(function (datum) { - return (datum.ordered && datum.ordered.date); - }); - }; + _isTimeBasedChart(selection) { + const data = selection.data(); + return data.every(function (datum) { + return (datum.ordered && datum.ordered.date); + }); + }; - TimeMarker.prototype.render = function (selection) { - let self = this; + render(selection) { + const self = this; - // return if not time based chart - if (!self._isTimeBasedChart(selection)) return; + // return if not time based chart + if (!self._isTimeBasedChart(selection)) return; - selection.each(function () { - d3.select(this).selectAll('time-marker') + selection.each(function () { + d3.select(this).selectAll('time-marker') .data(self.times) .enter().append('line') .attr('class', function (d) { @@ -66,8 +63,9 @@ export default function TimeMarkerFactory() { }) .attr('y1', self.height) .attr('y2', self.xScale.range()[0]); - }); - }; + }); + }; + } return TimeMarker; };