mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Backport PR #8186
--------- **Commit 1:** converting to ES6 class syntax * Original sha:161ba75d46
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-09-08T14:28:44Z **Commit 2:** let to const * Original sha:9cb6b60fe0
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-09-08T14:54:41Z **Commit 3:** fixing indentation to match our style * Original sha:0b63b830f2
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-09-08T15:08:56Z **Commit 4:** removing unused variables/imports * Original sha:93295012fc
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-09-08T15:23:24Z
This commit is contained in:
parent
e86b355b40
commit
47911d6fc5
80 changed files with 4930 additions and 5028 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)();
|
||||
|
|
|
@ -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'
|
||||
];
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('Vislib Split Function Test Suite', function () {
|
|||
let xAxisSplit;
|
||||
let yAxisSplit;
|
||||
let el;
|
||||
let data = {
|
||||
const data = {
|
||||
rows: [
|
||||
{
|
||||
hits : 621,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 = $('<div>').css({
|
||||
const node = $('<div>').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'
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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 : {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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: '<div class=chart1></div>',
|
||||
type: 'pie',
|
||||
addLegend: true,
|
||||
addTooltip: true
|
||||
};
|
||||
let visLibParams2 = {
|
||||
const visLibParams2 = {
|
||||
el: '<div class=chart2></div>',
|
||||
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
|
||||
});
|
||||
|
|
|
@ -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 = $('<div>');
|
||||
const $mockMapEl = $('<div>');
|
||||
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]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = $('<div>');
|
||||
const mockChartEl = $('<div>');
|
||||
|
||||
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 () {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -70,4 +70,4 @@ define((require) => (Private, config, $rootScope) => {
|
|||
}
|
||||
|
||||
return new MappedColors();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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 = $('<div>');
|
||||
const $el = $('<div>');
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 || [];
|
||||
/**
|
||||
* 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;
|
||||
|
||||
this.binder.jqOn(vis.el, 'mouseenter', '.vis-alerts-tray', function () {
|
||||
let $tray = $(this);
|
||||
hide();
|
||||
$(vis.el).on('mousemove', checkForExit);
|
||||
const alerts = _(this.alertDefs)
|
||||
.map(function (alertDef) {
|
||||
if (!alertDef) return;
|
||||
if (alertDef.test && !alertDef.test(vis, data)) return;
|
||||
|
||||
function hide() {
|
||||
$tray.css({
|
||||
'pointer-events': 'none',
|
||||
opacity: 0.3
|
||||
});
|
||||
}
|
||||
const type = alertDef.type || 'info';
|
||||
const icon = alertDef.icon || type;
|
||||
const msg = alertDef.msg;
|
||||
|
||||
function show() {
|
||||
$(vis.el).off('mousemove', checkForExit);
|
||||
$tray.css({
|
||||
'pointer-events': 'auto',
|
||||
opacity: 1
|
||||
});
|
||||
}
|
||||
// alert container
|
||||
const $icon = $('<i>').addClass('vis-alerts-icon fa fa-' + icon);
|
||||
const $text = $('<p>').addClass('vis-alerts-text').text(msg);
|
||||
|
||||
function checkForExit(event) {
|
||||
let pos = $tray.offset();
|
||||
if (pos.top > event.clientY || pos.left > event.clientX) return show();
|
||||
return $('<div>').addClass('vis-alert vis-alert-' + type).append([$icon, $text]);
|
||||
})
|
||||
.compact();
|
||||
|
||||
let bottom = pos.top + $tray.height();
|
||||
if (event.clientY > bottom) return show();
|
||||
if (!alerts.size()) return;
|
||||
|
||||
let right = pos.left + $tray.width();
|
||||
if (event.clientX > right) return show();
|
||||
}
|
||||
});
|
||||
$(vis.el).find('.vis-alerts').append(
|
||||
$('<div>').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 = $('<i>').addClass('vis-alerts-icon fa fa-' + icon);
|
||||
let $text = $('<p>').addClass('vis-alerts-text').text(msg);
|
||||
|
||||
return $('<div>').addClass('vis-alert vis-alert-' + type).append([$icon, $text]);
|
||||
})
|
||||
.compact();
|
||||
|
||||
if (!alerts.size()) return;
|
||||
|
||||
$(vis.el).find('.vis-alerts').append(
|
||||
$('<div>').addClass('vis-alerts-tray').append(alerts.value())
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tear down the Alerts
|
||||
* @return {undefined}
|
||||
*/
|
||||
Alerts.prototype.destroy = function () {
|
||||
this.binder.destroy();
|
||||
};
|
||||
|
||||
return Alerts;
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
return function (selection) {
|
||||
selection.each(function () {
|
||||
let el = this;
|
||||
let div = d3.select(el);
|
||||
let width = $(el).width();
|
||||
let height = $(el).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);
|
||||
});
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
draw(title) {
|
||||
const self = this;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
this.el = el;
|
||||
this.tooltip = new Tooltip('chart-title', el, function (d) {
|
||||
return '<p>' + _.escape(d.label) + '</p>';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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
|
||||
*/
|
||||
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();
|
||||
class ChartTitle extends ErrorHandler {
|
||||
constructor(el) {
|
||||
super();
|
||||
this.el = el;
|
||||
this.tooltip = new Tooltip('chart-title', el, function (d) {
|
||||
return '<p>' + _.escape(d.label) + '</p>';
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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 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;
|
||||
return d3.select(this.el).selectAll('.chart-title').call(this.draw(width, height));
|
||||
};
|
||||
|
||||
self.validateWidthandHeight(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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
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 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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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');
|
||||
*/
|
||||
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');
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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 (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);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
function hover(d, i) {
|
||||
// Add pointer if item is clickable
|
||||
if (isClickable) {
|
||||
self.addMousePointer.call(this, arguments);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return addEvent('click', click);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 Boolean(hasTimeField && xAxis.ordered && xAxis.xScale && _.isFunction(xAxis.xScale.invert));
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if brushing is currently enabled
|
||||
*
|
||||
* @method isBrushable
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Dispatch.prototype.isBrushable = function () {
|
||||
return this.allowBrushing() && this.listenerCount('brush') > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param svg
|
||||
* @returns {Function}
|
||||
*/
|
||||
Dispatch.prototype.addBrushEvent = function (svg) {
|
||||
if (!this.isBrushable()) return;
|
||||
|
||||
let xScale = this.handler.xAxis.xScale;
|
||||
let yScale = this.handler.xAxis.yScale;
|
||||
let brush = this.createBrush(xScale, svg);
|
||||
|
||||
function brushEnd() {
|
||||
if (!validBrushClick(d3.event)) return;
|
||||
|
||||
let bar = d3.select(this);
|
||||
let startX = d3.mouse(svg.node());
|
||||
let 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);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mouseover Behavior
|
||||
*
|
||||
* @method addMousePointer
|
||||
* @returns {D3.Selection}
|
||||
*/
|
||||
Dispatch.prototype.addMousePointer = function () {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouseout Behavior
|
||||
*
|
||||
* @param element {D3.Selection}
|
||||
* @method unHighlight
|
||||
*/
|
||||
Dispatch.prototype.unHighlight = function (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;
|
||||
|
||||
return self.emit('brush', {
|
||||
range: range,
|
||||
config: attr,
|
||||
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,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
handler: handler
|
||||
};
|
||||
|
||||
// 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);
|
||||
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) + '%';
|
||||
}
|
||||
}
|
||||
|
||||
return eventData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
})
|
||||
.selectAll('rect')
|
||||
.attr('height', height - margin.top - margin.bottom);
|
||||
};
|
||||
};
|
||||
|
||||
return brush;
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
function hover(d, i) {
|
||||
// Add pointer if item is clickable
|
||||
if (isClickable) {
|
||||
self.addMousePointer.call(this, arguments);
|
||||
}
|
||||
|
||||
self.handler.highlight.call(this, $el);
|
||||
self.emit('hover', self.eventResponse(d, i));
|
||||
}
|
||||
|
||||
return addEvent('mouseover', hover);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
function mouseout() {
|
||||
self.handler.unHighlight.call(this, $el);
|
||||
}
|
||||
|
||||
return addEvent('mouseout', mouseout);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @method addClickEvent
|
||||
* @returns {Function}
|
||||
*/
|
||||
addClickEvent() {
|
||||
const self = this;
|
||||
const addEvent = this.addEvent;
|
||||
|
||||
function click(d, i) {
|
||||
self.emit('click', self.eventResponse(d, i));
|
||||
}
|
||||
|
||||
return addEvent('click', click);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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));
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if brushing is currently enabled
|
||||
*
|
||||
* @method isBrushable
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isBrushable() {
|
||||
return this.allowBrushing() && this.listenerCount('brush') > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @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);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mouseover Behavior
|
||||
*
|
||||
* @method addMousePointer
|
||||
* @returns {D3.Selection}
|
||||
*/
|
||||
addMousePointer() {
|
||||
return d3.select(this).style('cursor', 'pointer');
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
class Handler {
|
||||
constructor(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.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);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
if (!dataType) {
|
||||
throw new errors.NoResults();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
selection.selectAll('*').remove();
|
||||
|
||||
this._validateData();
|
||||
this.renderArray.forEach(function (property) {
|
||||
if (typeof property.render === 'function') {
|
||||
property.render();
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
this._attr = _.defaults(vis._attr || {}, {
|
||||
'margin': {top: 10, right: 3, bottom: 5, left: 3}
|
||||
});
|
||||
|
||||
charts.push(chart);
|
||||
chart.render();
|
||||
});
|
||||
};
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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');
|
||||
// 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);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Disables events for all charts
|
||||
*
|
||||
* @method disable
|
||||
* @param event {String} Event type
|
||||
* @param chart {Object} Chart
|
||||
* @returns {*}
|
||||
*/
|
||||
Handler.prototype.disable = chartEventProxyToggle('off');
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
function chartEventProxyToggle(method) {
|
||||
return function (event, chart) {
|
||||
let proxyHandler = this.getProxyHandler(event);
|
||||
/**
|
||||
* 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;
|
||||
|
||||
_.each(chart ? [chart] : this.charts, function (chart) {
|
||||
chart.events[method](event, proxyHandler);
|
||||
if (!dataType) {
|
||||
throw new errors.NoResults();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
});
|
||||
|
||||
// render the chart(s)
|
||||
selection.selectAll('.chart')
|
||||
.each(function (chartData) {
|
||||
const chart = new self.ChartClass(self, this, chartData);
|
||||
|
||||
self.vis.activeEvents().forEach(function (event) {
|
||||
self.enable(event, chart);
|
||||
});
|
||||
|
||||
charts.push(chart);
|
||||
chart.render();
|
||||
});
|
||||
};
|
||||
|
||||
chartEventProxyToggle(method) {
|
||||
return function (event, chart) {
|
||||
const proxyHandler = this.getProxyHandler(event);
|
||||
|
||||
_.each(chart ? [chart] : this.charts, function (chart) {
|
||||
chart.events[method](event, proxyHandler);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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('<i class="fa fa-meh-o"></i>')
|
||||
.append('h4').text(message);
|
||||
|
||||
div.append('div').attr('class', 'item bottom');
|
||||
return div;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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('<i class="fa fa-meh-o"></i>')
|
||||
.append('h4').text(message);
|
||||
|
||||
div.append('div').attr('class', 'item bottom');
|
||||
return div;
|
||||
}
|
||||
|
||||
return div.append('h4').text(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys all the charts in the visualization
|
||||
*
|
||||
* @method destroy
|
||||
*/
|
||||
Handler.prototype.destroy = function () {
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
render() {
|
||||
this.removeAll(this.el);
|
||||
this.createLayout(this.layoutType);
|
||||
};
|
||||
|
||||
// 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
|
||||
*/
|
||||
createLayout(arr) {
|
||||
const self = this;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
return _.each(arr, function (obj) {
|
||||
self.layout(obj);
|
||||
});
|
||||
};
|
||||
|
||||
this.createLayout(obj.children);
|
||||
}
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
return childEl;
|
||||
};
|
||||
if (!obj.type) {
|
||||
throw new Error('No element type provided');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 obj.type !== 'string') {
|
||||
throw new Error(obj.type + ' must be a string');
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
if (typeof obj.parent === 'string' && obj.parent.charAt(0) !== '.') {
|
||||
obj.parent = '.' + obj.parent;
|
||||
}
|
||||
|
||||
return d3.select(el)
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
*/
|
||||
YAxis.prototype.render = function () {
|
||||
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
|
||||
*/
|
||||
render() {
|
||||
d3.select(this.el).selectAll('.y-axis-div').call(this.draw());
|
||||
};
|
||||
|
||||
YAxis.prototype._isPercentage = function () {
|
||||
return (this._attr.mode === 'percentage');
|
||||
};
|
||||
_isPercentage() {
|
||||
return (this._attr.mode === 'percentage');
|
||||
};
|
||||
|
||||
YAxis.prototype._isUserDefined = function () {
|
||||
return (this._attr.setYExtents);
|
||||
};
|
||||
_isUserDefined() {
|
||||
return (this._attr.setYExtents);
|
||||
};
|
||||
|
||||
YAxis.prototype._isYExtents = function () {
|
||||
return (this._attr.defaultYExtents);
|
||||
};
|
||||
_isYExtents() {
|
||||
return (this._attr.defaultYExtents);
|
||||
};
|
||||
|
||||
YAxis.prototype._validateUserExtents = function (domain) {
|
||||
let self = this;
|
||||
_validateUserExtents(domain) {
|
||||
const 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;
|
||||
});
|
||||
};
|
||||
|
||||
YAxis.prototype._getExtents = function (domain) {
|
||||
let min = domain[0];
|
||||
let max = domain[1];
|
||||
_getExtents(domain) {
|
||||
const min = domain[0];
|
||||
const 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;
|
||||
};
|
||||
|
||||
YAxis.prototype._throwCustomError = function (message) {
|
||||
throw new Error(message);
|
||||
};
|
||||
_throwCustomError(message) {
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
YAxis.prototype._throwLogScaleValuesError = function () {
|
||||
throw new errors.InvalidLogScaleValues();
|
||||
};
|
||||
_throwLogScaleValuesError() {
|
||||
throw new errors.InvalidLogScaleValues();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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';
|
||||
/**
|
||||
* 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';
|
||||
|
||||
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}
|
||||
*/
|
||||
YAxis.prototype._logDomain = function (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}
|
||||
*/
|
||||
_logDomain(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
|
||||
*/
|
||||
YAxis.prototype.getYScale = function (height) {
|
||||
let scale = this._getScaleType(this._attr.scale);
|
||||
let domain = this._getExtents(this.domain);
|
||||
/**
|
||||
* 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]);
|
||||
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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
getScaleType() {
|
||||
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');
|
||||
};
|
||||
tickFormat() {
|
||||
const 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);
|
||||
};
|
||||
_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
|
||||
*/
|
||||
YAxis.prototype.getYAxis = function (height) {
|
||||
let yScale = this.getYScale(height);
|
||||
this._validateYScale(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()
|
||||
// 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 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 + ')');
|
||||
}
|
||||
}
|
||||
});
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!');
|
||||
// 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;
|
||||
|
||||
this.data = data;
|
||||
|
||||
if (!this.uiState) {
|
||||
this.uiState = uiState;
|
||||
uiState.on('change', this._uiStateChangeHandler = () => this.render(this.data, this.uiState));
|
||||
}
|
||||
|
||||
this.handler = handlerTypes[chartType](this) || handlerTypes.column(this);
|
||||
this._runWithoutResizeChecker('render');
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
throw error;
|
||||
if (!data) {
|
||||
throw new Error('No valid data!');
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
if (this.handler) {
|
||||
this.data = null;
|
||||
this._runOnHandler('destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.data = data;
|
||||
|
||||
this.binder.destroy();
|
||||
this.resizeChecker.destroy();
|
||||
if (this.uiState) this.uiState.off('change', this._uiStateChangeHandler);
|
||||
if (this.handler) this._runOnHandler('destroy');
|
||||
if (!this.uiState) {
|
||||
this.uiState = uiState;
|
||||
uiState.on('change', this._uiStateChangeHandler = () => this.render(this.data, this.uiState));
|
||||
}
|
||||
|
||||
selection.remove();
|
||||
selection = null;
|
||||
};
|
||||
this.handler = handlerTypes[chartType](this) || handlerTypes.column(this);
|
||||
this._runWithoutResizeChecker('render');
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
};
|
||||
if (this.handler && _.isFunction(this.handler.resize)) {
|
||||
this._runOnHandler('resize');
|
||||
} else {
|
||||
this.render(this.data, this.uiState);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
_runWithoutResizeChecker(method) {
|
||||
this.resizeChecker.stopSchedule();
|
||||
this._runOnHandler(method);
|
||||
this.resizeChecker.saveSize();
|
||||
this.resizeChecker.saveDirty(false);
|
||||
this.resizeChecker.continueSchedule();
|
||||
};
|
||||
|
||||
// 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);
|
||||
_runOnHandler(method) {
|
||||
try {
|
||||
this.handler[method]();
|
||||
} catch (error) {
|
||||
|
||||
return ret;
|
||||
};
|
||||
if (error instanceof errors.KbnError) {
|
||||
error.displayToScreen(this.handler);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
|
|
@ -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 = [];
|
||||
|
||||
const events = this.events = new Dispatch(handler);
|
||||
|
||||
if (_.get(this.handler, '_attr.addTooltip')) {
|
||||
const $el = this.handler.el;
|
||||
const formatter = this.handler.data.get('tooltipFormatter');
|
||||
|
||||
// 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.handler = handler;
|
||||
this.chartEl = el;
|
||||
this.chartData = chartData;
|
||||
this.tooltips = [];
|
||||
/**
|
||||
* Renders the chart(s)
|
||||
*
|
||||
* @method render
|
||||
* @returns {HTMLElement} Contains the D3 chart
|
||||
*/
|
||||
render() {
|
||||
const selection = d3.select(this.chartEl);
|
||||
|
||||
let events = this.events = new Dispatch(handler);
|
||||
selection.selectAll('*').remove();
|
||||
selection.call(this.draw());
|
||||
};
|
||||
|
||||
if (_.get(this.handler, '_attr.addTooltip')) {
|
||||
let $el = this.handler.el;
|
||||
let formatter = this.handler.data.get('tooltipFormatter');
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Add tooltip
|
||||
this.tooltip = new Tooltip('chart', $el, formatter, events);
|
||||
this.tooltips.push(this.tooltip);
|
||||
}
|
||||
function resolveLabel(datum) {
|
||||
if (labels.length === 1) return labels[0];
|
||||
if (datum[0]) return datum[0][labelProp];
|
||||
return datum[labelProp];
|
||||
}
|
||||
|
||||
this._attr = _.defaults(this.handler._attr || {}, {});
|
||||
this._addIdentifier = _.bind(this._addIdentifier, this);
|
||||
selection.each(function (datum) {
|
||||
const label = resolveLabel(datum);
|
||||
if (label != null) dataLabel(this, label);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
return Chart;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
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 || {};
|
||||
// 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,
|
||||
};
|
||||
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;
|
||||
|
||||
let self = this;
|
||||
let 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'
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this._boundingControl = new L.Control.Draw(drawOptions);
|
||||
this.map.addControl(this._boundingControl);
|
||||
};
|
||||
|
||||
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('<a class="fa fa-crop" href="#" title="Fit Data Bounds"></a>')
|
||||
.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
self._fitBounds();
|
||||
});
|
||||
|
||||
return fitContainer;
|
||||
},
|
||||
onRemove: function (map) {
|
||||
$(fitContainer).off('click');
|
||||
}
|
||||
});
|
||||
|
||||
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 = '<h2>' + _.escape(mapLabel) + '</h2>';
|
||||
};
|
||||
|
||||
// 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');
|
||||
this._createMap(mapOptions);
|
||||
}
|
||||
};
|
||||
|
||||
TileMapMap.prototype.updateSize = function () {
|
||||
this.map.invalidateSize({
|
||||
debounceMoveend: true
|
||||
});
|
||||
};
|
||||
addBoundingControl() {
|
||||
if (this._boundingControl) return;
|
||||
|
||||
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;
|
||||
};
|
||||
const self = this;
|
||||
const drawOptions = {draw: {}};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
TileMapMap.prototype._attachEvents = function () {
|
||||
let self = this;
|
||||
let saturateTiles = self.saturateTiles.bind(self);
|
||||
|
||||
this._tileLayer.on('tileload', saturateTiles);
|
||||
|
||||
this.map.on('unload', function () {
|
||||
self._tileLayer.off('tileload', saturateTiles);
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
|
||||
// TODO: Different drawTypes need differ info. Need a switch on the object creation
|
||||
let 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
|
||||
}
|
||||
_.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'
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.map.on('zoomend', function () {
|
||||
if (!self.map) return;
|
||||
self._mapZoom = self.map.getZoom();
|
||||
if (!self._events) return;
|
||||
this._boundingControl = new L.Control.Draw(drawOptions);
|
||||
this.map.addControl(this._boundingControl);
|
||||
};
|
||||
|
||||
self._events.emit('mapZoomEnd', {
|
||||
chart: self._chartData,
|
||||
map: self.map,
|
||||
zoom: self._mapZoom,
|
||||
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('<a class="fa fa-crop" href="#" title="Fit Data Bounds"></a>')
|
||||
.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
self._fitBounds();
|
||||
});
|
||||
|
||||
return fitContainer;
|
||||
},
|
||||
onRemove: function () {
|
||||
$(fitContainer).off('click');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
TileMapMap.prototype._createMap = function (mapOptions) {
|
||||
if (this.map) this.destroy();
|
||||
this._fitControl = new FitControl();
|
||||
this.map.addControl(this._fitControl);
|
||||
};
|
||||
|
||||
// add map tiles layer, using the mapTiles object settings
|
||||
if (this._attr.wms && this._attr.wms.enabled) {
|
||||
_.assign(mapOptions, {
|
||||
minZoom: 1,
|
||||
maxZoom: 18
|
||||
/**
|
||||
* 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 = '<h2>' + _.escape(mapLabel) + '</h2>';
|
||||
};
|
||||
|
||||
// label.addTo(this.map);
|
||||
this.map.addControl(label);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
};
|
||||
|
||||
updateSize() {
|
||||
this.map.invalidateSize({
|
||||
debounceMoveend: true
|
||||
});
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
this.map = L.map(this._container, mapOptions);
|
||||
this._attachEvents();
|
||||
this._addMarkers();
|
||||
};
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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());
|
||||
};
|
||||
this._markers = this._createMarkers({
|
||||
tooltipFormatter: this._tooltipFormatter,
|
||||
valueFormatter: this._valueFormatter,
|
||||
attr: this._attr
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the Rectangles representing the geohash grid
|
||||
*
|
||||
* @return {LatLngRectangles[]}
|
||||
*/
|
||||
TileMapMap.prototype._getDataRectangles = function () {
|
||||
if (!this._geoJson) return [];
|
||||
return _.pluck(this._geoJson.features, 'properties.rectangle');
|
||||
};
|
||||
if (this._geoJson.features.length > 1) {
|
||||
this._markers.addLegend();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
_attachEvents() {
|
||||
const self = this;
|
||||
const saturateTiles = self.saturateTiles.bind(self);
|
||||
|
||||
this._tileLayer.on('tileload', saturateTiles);
|
||||
|
||||
this.map.on('unload', function () {
|
||||
self._tileLayer.off('tileload', saturateTiles);
|
||||
});
|
||||
|
||||
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) {
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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[]}
|
||||
*/
|
||||
_getDataRectangles() {
|
||||
if (!this._geoJson) return [];
|
||||
return _.pluck(this._geoJson.features, 'properties.rectangle');
|
||||
};
|
||||
}
|
||||
|
||||
return TileMapMap;
|
||||
};
|
||||
|
|
|
@ -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'));
|
||||
const Chart = Private(VislibVisualizationsChartProvider);
|
||||
const Tooltip = Private(VislibComponentsTooltipProvider);
|
||||
const 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);
|
||||
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 (firstStack) {
|
||||
currentStackOffsets = [0, 0];
|
||||
}
|
||||
if (lastStack) currentStackIndex = 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
|
||||
*/
|
||||
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)
|
||||
};
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
};
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
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
|
||||
if (y >= 0) {
|
||||
d.y0 = currentStackOffsets[1];
|
||||
currentStackOffsets[1] += y;
|
||||
} else {
|
||||
d.y0 = currentStackOffsets[0];
|
||||
currentStackOffsets[0] += y;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)
|
||||
};
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
validateDataCompliesWithScalingMethod(data) {
|
||||
const valuesSmallerThanOne = function (d) {
|
||||
return d.values && d.values.some(e => e.y < 1);
|
||||
};
|
||||
|
||||
const invalidLogScale = data.series && data.series.some(valuesSmallerThanOne);
|
||||
if (this._attr.scale === 'log' && invalidLogScale) {
|
||||
throw new errors.InvalidLogScaleValues();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// the min and max that the endzones start in
|
||||
const min = leftEndzone.w;
|
||||
const 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));
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
endzoneTT.render()(svg);
|
||||
};
|
||||
}
|
||||
|
||||
return PointSeriesChart;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
class AreaChart extends PointSeriesChart {
|
||||
constructor(handler, chartEl, chartData) {
|
||||
super(handler, chartEl, chartData);
|
||||
|
||||
AreaChart.Super.apply(this, arguments);
|
||||
this.isOverlapping = (handler._attr.mode === 'overlap');
|
||||
|
||||
this.isOverlapping = (handler._attr.mode === 'overlap');
|
||||
if (this.isOverlapping) {
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
//The legend should keep max opacity
|
||||
$('[data-label]', $(element).siblings()).css('opacity', 1);
|
||||
};
|
||||
}
|
||||
|
||||
this.checkIfEnoughData();
|
||||
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
xValue: function (d) { return d.x; },
|
||||
yValue: function (d) { return d.y; }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
//The legend should keep max opacity
|
||||
$('[data-label]', $(element).siblings()).css('opacity', 1);
|
||||
};
|
||||
}
|
||||
|
||||
return yScale(d.y0);
|
||||
})
|
||||
.y1(function (d) {
|
||||
if (isOverlapping) {
|
||||
return yScale(d.y);
|
||||
}
|
||||
this.checkIfEnoughData();
|
||||
|
||||
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);
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
xValue: function (d) {
|
||||
return d.x;
|
||||
},
|
||||
yValue: function (d) {
|
||||
return d.y;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return attachedEvents;
|
||||
};
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return yScale(d.y0);
|
||||
})
|
||||
.y1(function (d) {
|
||||
if (isOverlapping) {
|
||||
return yScale(d.y);
|
||||
}
|
||||
|
||||
layer = svg.selectAll('.points')
|
||||
.data(data)
|
||||
.enter()
|
||||
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;
|
||||
});
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
// 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
|
||||
*/
|
||||
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 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
|
||||
*/
|
||||
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();
|
||||
// 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);
|
||||
// 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');
|
||||
|
||||
if (isWiggle && !ordered) throw new errors.InvalidWiggleSelection();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// update
|
||||
circles
|
||||
.attr('cx', function cx(d) {
|
||||
if (ordered && ordered.date) {
|
||||
return xScale(d.x);
|
||||
}
|
||||
|
||||
if (width < minWidth || height < minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
return xScale(d.x) + xScale.rangeBand() / 2;
|
||||
})
|
||||
.attr('cy', function cy(d) {
|
||||
if (isOverlapping) {
|
||||
return yScale(d.y);
|
||||
}
|
||||
self.validateWiggleSelection();
|
||||
return yScale(d.y0 + d.y);
|
||||
})
|
||||
.attr('r', circleRadius);
|
||||
|
||||
// Select the current DOM element
|
||||
div = d3.select(this);
|
||||
// Add tooltip
|
||||
if (isTooltip) {
|
||||
circles.call(tooltip.render());
|
||||
}
|
||||
|
||||
// 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 + ')');
|
||||
return circles;
|
||||
};
|
||||
|
||||
// add clipPath to hide circles when they go out of bounds
|
||||
self.addClipPath(svg, width, height);
|
||||
self.createEndZones(svg);
|
||||
/**
|
||||
* 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();
|
||||
|
||||
// add path
|
||||
path = self.addPath(svg, layers);
|
||||
// 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);
|
||||
};
|
||||
|
||||
if (yMin < 0 && self._attr.mode !== 'wiggle' && self._attr.mode !== 'silhouette') {
|
||||
checkIfEnoughData() {
|
||||
const series = this.chartData.series;
|
||||
const message = 'Area charts require more than one data point. Try adding ' +
|
||||
'an X-Axis Aggregation';
|
||||
|
||||
// Draw line at yScale 0 value
|
||||
svg.append('line')
|
||||
const notEnoughData = series.some(function (obj) {
|
||||
return obj.values.length < 2;
|
||||
});
|
||||
|
||||
if (notEnoughData) {
|
||||
throw new errors.NotEnoughData(message);
|
||||
}
|
||||
};
|
||||
|
||||
validateWiggleSelection() {
|
||||
const isWiggle = this._attr.mode === 'wiggle';
|
||||
const ordered = this.handler.data.get('ordered');
|
||||
|
||||
if (isWiggle && !ordered) throw new errors.InvalidWiggleSelection();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 circles
|
||||
const circles = self.addCircles(svg, layers);
|
||||
|
||||
// add click and hover events to circles
|
||||
self.addCircleEvents(circles, svg);
|
||||
// add click and hover events to circles
|
||||
self.addCircleEvents(circles, svg);
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
if (addTimeMarker) {
|
||||
timeMarker.render(svg);
|
||||
}
|
||||
|
||||
return svg;
|
||||
});
|
||||
return svg;
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return AreaChart;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
class ColumnChart extends PointSeriesChart {
|
||||
constructor(handler, chartEl, chartData) {
|
||||
super(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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
// Column chart specific attributes
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
xValue: function (d) {
|
||||
return d.x;
|
||||
},
|
||||
yValue: function (d) {
|
||||
return d.y;
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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}
|
||||
*/
|
||||
updateBars(bars) {
|
||||
const 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}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
// 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}
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 attachedEvents;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.`);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
// Line chart specific attributes
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
interpolate: 'linear',
|
||||
xValue: function (d) { return d.x; },
|
||||
yValue: function (d) { return d.y; }
|
||||
});
|
||||
}
|
||||
if (isBrushable) {
|
||||
attachedEvents.call(brush);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return attachedEvents;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
return attachedEvents;
|
||||
};
|
||||
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
|
||||
});
|
||||
|
||||
/**
|
||||
* 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;
|
||||
const radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this._attr.radiusRatio, 2);
|
||||
|
||||
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;
|
||||
// If only 1 point exists, show circle
|
||||
if (!showCircles && !isVisible) return 'none';
|
||||
return cColor(d);
|
||||
}
|
||||
|
||||
return _.min([Math.sqrt((circleRadius || 2) + 2), width, height]) + (modifier || 0);
|
||||
};
|
||||
}
|
||||
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);
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker = new TimeMarker(times, xScale, height);
|
||||
}
|
||||
|
||||
|
||||
|
||||
div = d3.select(el);
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker.render(svg);
|
||||
}
|
||||
|
||||
return svg;
|
||||
});
|
||||
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
|
||||
*/
|
||||
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);
|
||||
});
|
||||
|
||||
const 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
|
||||
*/
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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 = [];
|
||||
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 || {};
|
||||
this._tooltipFormatter = params.tooltipFormatter || _.identity;
|
||||
this._valueFormatter = params.valueFormatter || _.identity;
|
||||
this._attr = params.attr || {};
|
||||
|
||||
// set up the default legend colors
|
||||
this.quantizeLegendColors();
|
||||
}
|
||||
// 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;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
let self = this;
|
||||
const self = this;
|
||||
|
||||
// create the legend control, keep a reference
|
||||
self._legend = L.control({position: 'bottomright'});
|
||||
// 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 = $('<div>').addClass('tilemap-legend');
|
||||
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 = $('<div>').addClass('tilemap-legend');
|
||||
|
||||
_.each(self._legendColors, function (color, i) {
|
||||
let labelText = self._legendQuantizer
|
||||
.invertExtent(color)
|
||||
.map(self._valueFormatter)
|
||||
.join(' – ');
|
||||
_.each(self._legendColors, function (color, i) {
|
||||
const labelText = self._legendQuantizer
|
||||
.invertExtent(color)
|
||||
.map(self._valueFormatter)
|
||||
.join(' – ');
|
||||
|
||||
let label = $('<div>').text(labelText);
|
||||
const label = $('<div>').text(labelText);
|
||||
|
||||
let icon = $('<i>').css({
|
||||
background: color,
|
||||
'border-color': self.darkerColor(color)
|
||||
const icon = $('<i>').css({
|
||||
background: color,
|
||||
'border-color': self.darkerColor(color)
|
||||
});
|
||||
|
||||
label.append(icon);
|
||||
$div.append(label);
|
||||
});
|
||||
|
||||
label.append(icon);
|
||||
$div.append(label);
|
||||
return $div.get(0);
|
||||
};
|
||||
|
||||
self._legend.addTo(self.map);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
return $div.get(0);
|
||||
self.popups.push(popup);
|
||||
};
|
||||
|
||||
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
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
destroy() {
|
||||
const 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();
|
||||
}
|
||||
self._showTooltip(feature);
|
||||
},
|
||||
mouseout: function (e) {
|
||||
self._hidePopup();
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// remove marker layer from map
|
||||
if (self._markerGroup) {
|
||||
self.map.removeLayer(self._markerGroup);
|
||||
self._markerGroup = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
BaseMarker.prototype._addToMap = function () {
|
||||
this.map.addLayer(this._markerGroup);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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()
|
||||
// remove marker layer from map
|
||||
if (self._markerGroup) {
|
||||
self.map.removeLayer(self._markerGroup);
|
||||
self._markerGroup = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
this._markerGroup = L.geoJson(this.geoJson, _.defaults(defaultOptions, options));
|
||||
this._addToMap();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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()
|
||||
};
|
||||
|
||||
let content = this._tooltipFormatter(feature);
|
||||
this._markerGroup = L.geoJson(this.geoJson, _.defaults(defaultOptions, options));
|
||||
this._addToMap();
|
||||
};
|
||||
|
||||
if (!content) return;
|
||||
this._createTooltip(content, latLng);
|
||||
};
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
};
|
||||
|
||||
BaseMarker.prototype._createTooltip = function (content, latLng) {
|
||||
L.popup({autoPan: false})
|
||||
.setLatLng(latLng)
|
||||
.setContent(content)
|
||||
.openOn(this.map);
|
||||
};
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Closes the tooltip on the map
|
||||
*
|
||||
* @method _hidePopup
|
||||
* @return undefined
|
||||
*/
|
||||
BaseMarker.prototype._hidePopup = function () {
|
||||
if (!this.map) return;
|
||||
const content = this._tooltipFormatter(feature);
|
||||
|
||||
this.map.closePopup();
|
||||
};
|
||||
if (!content) return;
|
||||
this._createTooltip(content, latLng);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
_createTooltip(content, latLng) {
|
||||
L.popup({autoPan: false})
|
||||
.setLatLng(latLng)
|
||||
.setContent(content)
|
||||
.openOn(this.map);
|
||||
};
|
||||
|
||||
let reds1 = ['#ff6128'];
|
||||
let reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c'];
|
||||
let reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026'];
|
||||
let bottomCutoff = 2;
|
||||
let middleCutoff = 24;
|
||||
/**
|
||||
* Closes the tooltip on the map
|
||||
*
|
||||
* @method _hidePopup
|
||||
* @return undefined
|
||||
*/
|
||||
_hidePopup() {
|
||||
if (!this.map) return;
|
||||
|
||||
if (max - min <= bottomCutoff) {
|
||||
this._legendColors = reds1;
|
||||
} else if (max - min <= middleCutoff) {
|
||||
this._legendColors = reds3;
|
||||
} else {
|
||||
this._legendColors = reds5;
|
||||
}
|
||||
this.map.closePopup();
|
||||
};
|
||||
|
||||
this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors);
|
||||
};
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
return BaseMarker;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
function mouseMoveLocation(e) {
|
||||
const latlng = e.latlng;
|
||||
|
||||
this.map.closePopup();
|
||||
|
||||
// unhighlight all svgs
|
||||
d3.selectAll('path.geohash', this.chartEl).classed('geohash-hover', false);
|
||||
|
||||
if (!this.geoJson.features.length || this._disableTooltips) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find nearest feature to event latlng
|
||||
const feature = this._nearestFeature(latlng);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// find nearest feature to event latlng
|
||||
let feature = this._nearestFeature(latlng);
|
||||
_.reduce(this.geoJson.features, function (distance, feature) {
|
||||
const featureLatLng = self._getLatLng(feature);
|
||||
const dist = latLng.distanceTo(featureLatLng);
|
||||
|
||||
// show tooltip if close enough to event latlng
|
||||
if (this._tooltipProximity(latlng, feature)) {
|
||||
this._showTooltip(feature, latlng);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (dist < distance) {
|
||||
nearest = feature;
|
||||
return dist;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(',');
|
||||
});
|
||||
return distance;
|
||||
}, Infinity);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return nearest;
|
||||
};
|
||||
|
||||
if (latLng.lng < -180 || latLng.lng > 180) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
|
||||
_.reduce(this.geoJson.features, function (distance, feature) {
|
||||
let featureLatLng = self._getLatLng(feature);
|
||||
let dist = latLng.distanceTo(featureLatLng);
|
||||
let showTip = false;
|
||||
const featureLatLng = this._getLatLng(feature);
|
||||
|
||||
if (dist < distance) {
|
||||
nearest = feature;
|
||||
return dist;
|
||||
// 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 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;
|
||||
};
|
||||
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;
|
||||
/**
|
||||
* 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) {
|
||||
let lat = feature.properties.center[0];
|
||||
let lng = feature.properties.center[1];
|
||||
let heatIntensity;
|
||||
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;
|
||||
}
|
||||
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 [lat, lng, heatIntensity];
|
||||
});
|
||||
};
|
||||
|
||||
return HeatmapMarker;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
class ScaledCircleMarker extends BaseMarker {
|
||||
constructor(map, geoJson, params) {
|
||||
super(map, geoJson, params);
|
||||
|
||||
// multiplier to reduce size of all circles
|
||||
let scaleFactor = 0.6;
|
||||
// multiplier to reduce size of all circles
|
||||
const 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);
|
||||
}
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
*/
|
||||
_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]
|
||||
|
||||
// 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]]);
|
||||
|
||||
// get latLng of geohash center point
|
||||
const 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]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* _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');
|
||||
|
||||
// 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
|
||||
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
|
||||
let center = L.latLng([centerPoint[0], centerPoint[1]]);
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
class PieChart extends Chart {
|
||||
constructor(handler, chartEl, chartData) {
|
||||
super(handler, chartEl, chartData);
|
||||
|
||||
const charts = this.handler.data.getVisData();
|
||||
this._validatePieData(charts);
|
||||
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
isDonut: handler._attr.isDonut || false
|
||||
});
|
||||
}
|
||||
PieChart.Super.apply(this, arguments);
|
||||
|
||||
let charts = this.handler.data.getVisData();
|
||||
this._validatePieData(charts);
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
|
||||
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.
|
||||
*/
|
||||
PieChart.prototype._validatePieData = function (charts) {
|
||||
let 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
|
||||
*/
|
||||
addPathEvents(element) {
|
||||
const events = this.events;
|
||||
|
||||
if (isAllZeros) { throw new errors.PieContainsAllZeros(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
return element
|
||||
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;
|
||||
return Math.max(0, y(d.y));
|
||||
})
|
||||
.outerRadius(function (d) {
|
||||
return Math.max(0, y(d.y + d.dy));
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
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;
|
||||
|
||||
self.convertToPercentage(slices);
|
||||
self._validateContainerSize(width, height);
|
||||
|
||||
let svg = div.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
|
||||
path = self.addPath(width, height, svg, slices);
|
||||
self.addPathEvents(path);
|
||||
|
||||
return svg;
|
||||
});
|
||||
return path;
|
||||
};
|
||||
};
|
||||
|
||||
_validateContainerSize(width, height) {
|
||||
const minWidth = 20;
|
||||
const minHeight = 20;
|
||||
|
||||
if (width <= minWidth || height <= minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
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();
|
||||
}
|
||||
|
||||
TileMap.Super.apply(this, arguments);
|
||||
/**
|
||||
* Draws tile map, called on chart render
|
||||
*
|
||||
* @method draw
|
||||
* @return {Function} - function to add a map to a selection
|
||||
*/
|
||||
draw() {
|
||||
const self = this;
|
||||
|
||||
// track the map objects
|
||||
this.maps = [];
|
||||
this._chartData = chartData || {};
|
||||
_.assign(this, this._chartData);
|
||||
// clean up old maps
|
||||
self.destroy();
|
||||
|
||||
this._appendGeoExtents();
|
||||
}
|
||||
return function (selection) {
|
||||
selection.each(function () {
|
||||
self._appendMap(this);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// clean up old maps
|
||||
self.destroy();
|
||||
|
||||
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')
|
||||
} : {};
|
||||
|
||||
/**
|
||||
* 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 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
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
let currentTimeArr = [{
|
||||
'time': new Date().getTime(),
|
||||
'class': 'time-marker',
|
||||
'color': '#c80000',
|
||||
'opacity': 0.3,
|
||||
'width': 2
|
||||
}];
|
||||
_isTimeBasedChart(selection) {
|
||||
const data = selection.data();
|
||||
return data.every(function (datum) {
|
||||
return (datum.ordered && datum.ordered.date);
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
render(selection) {
|
||||
const self = this;
|
||||
|
||||
TimeMarker.prototype._isTimeBasedChart = function (selection) {
|
||||
let data = selection.data();
|
||||
return data.every(function (datum) {
|
||||
return (datum.ordered && datum.ordered.date);
|
||||
});
|
||||
};
|
||||
// return if not time based chart
|
||||
if (!self._isTimeBasedChart(selection)) return;
|
||||
|
||||
TimeMarker.prototype.render = function (selection) {
|
||||
let self = this;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue