mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' into fix/back-button
This commit is contained in:
commit
df23e61e5f
67 changed files with 1433 additions and 437 deletions
|
@ -39,6 +39,7 @@
|
|||
"inflection": "~1.3.5",
|
||||
"jquery": "~2.1.0",
|
||||
"leaflet": "0.7.3",
|
||||
"Leaflet.heat": "Leaflet/Leaflet.heat#627ede7c11bbe43",
|
||||
"lesshat": "~3.0.2",
|
||||
"lodash": "~2.4.1",
|
||||
"moment": "~2.9.0",
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<table>
|
||||
<tbody>
|
||||
<tr ng-repeat="detail in details" >
|
||||
<td><b>{{detail.label}}</b></td>
|
||||
<td>{{detail.value}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -0,0 +1,32 @@
|
|||
define(function (require) {
|
||||
return function TileMapTooltipFormatter($compile, $rootScope) {
|
||||
var $ = require('jquery');
|
||||
|
||||
var $tooltipScope = $rootScope.$new();
|
||||
var $tooltip = $(require('text!components/agg_response/geo_json/_tooltip.html'));
|
||||
$compile($tooltip)($tooltipScope);
|
||||
|
||||
return function tooltipFormatter(feature) {
|
||||
if (!feature) return '';
|
||||
|
||||
var details = $tooltipScope.details = [];
|
||||
|
||||
var lat = feature.geometry.coordinates[1];
|
||||
var lng = feature.geometry.coordinates[0];
|
||||
|
||||
var metric = {
|
||||
label: feature.properties.valueLabel,
|
||||
value: feature.properties.count
|
||||
};
|
||||
var location = {
|
||||
label: 'Center',
|
||||
value: lat.toFixed(4) + ', ' + lng.toFixed(4)
|
||||
};
|
||||
|
||||
details.push(metric, location);
|
||||
|
||||
$tooltipScope.$apply();
|
||||
return $tooltip[0].outerHTML;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -3,6 +3,8 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
|
||||
var readRows = require('components/agg_response/geo_json/_read_rows');
|
||||
var tooltipFormatter = Private(require('components/agg_response/geo_json/_tooltip_formatter'));
|
||||
|
||||
function findCol(table, name) {
|
||||
return _.findIndex(table.columns, function (col) {
|
||||
return col.aggConfig.schema.name === name;
|
||||
|
@ -25,6 +27,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
var chart = {};
|
||||
chart.tooltipFormatter = tooltipFormatter;
|
||||
var geoJson = chart.geoJson = {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
|
@ -37,6 +40,10 @@ define(function (require) {
|
|||
max: 0
|
||||
};
|
||||
|
||||
if (agg.metric._opts.params && agg.metric._opts.params.field) {
|
||||
props.metricField = agg.metric._opts.params.field;
|
||||
}
|
||||
|
||||
// set precision from the bucketting column, if we have one
|
||||
if (agg.geo) {
|
||||
props.precision = _.parseInt(agg.geo.params.precision);
|
||||
|
|
|
@ -3,7 +3,7 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
var BucketAggType = Private(require('components/agg_types/buckets/_bucket_agg_type'));
|
||||
var defaultPrecision = 3;
|
||||
var defaultPrecision = 2;
|
||||
|
||||
function getPrecision(precision) {
|
||||
var maxPrecision = _.parseInt(config.get('visualization:tileMap:maxPrecision'));
|
||||
|
@ -29,10 +29,23 @@ define(function (require) {
|
|||
name: 'field',
|
||||
filterFieldTypes: 'geo_point'
|
||||
},
|
||||
{
|
||||
name: 'autoPrecision',
|
||||
default: true,
|
||||
write: _.noop
|
||||
},
|
||||
{
|
||||
name: 'precision',
|
||||
default: defaultPrecision,
|
||||
editor: require('text!components/agg_types/controls/precision.html'),
|
||||
controller: function ($scope) {
|
||||
$scope.$watchMulti([
|
||||
'agg.params.autoPrecision',
|
||||
'outputAgg.params.precision'
|
||||
], function (cur, prev) {
|
||||
if (cur[1]) $scope.agg.params.precision = cur[1];
|
||||
});
|
||||
},
|
||||
deserialize: getPrecision,
|
||||
write: function (aggConfig, output) {
|
||||
output.params.precision = getPrecision(aggConfig.params.precision);
|
||||
|
@ -41,4 +54,4 @@ define(function (require) {
|
|||
]
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<div>
|
||||
<small><a target="_window" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#date-math">Accepted Date Formats <i class="fa-link fa"></i></a></small>
|
||||
|
||||
<table class="vis-editor-agg-editor-ranges form-group">
|
||||
<table class="vis-editor-agg-editor-ranges form-group" ng-show="agg.params.ranges.length">
|
||||
<tr>
|
||||
<th>
|
||||
<label>From</label>
|
||||
|
@ -37,8 +35,23 @@
|
|||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<small>
|
||||
<a target="_window" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#date-math">Accepted Date Formats <i class="fa-link fa"></i></a>
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input ng-model="agg.params.ranges.length" name="rangeLength" required min="1" type="number" class="ng-hide" />
|
||||
<div class="hintbox" ng-show="aggForm.rangeLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one date range.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click="agg.params.ranges.push({})"
|
||||
class="sidebar-item-button primary">
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
name="field"
|
||||
required
|
||||
ng-model="agg.params.field"
|
||||
ng-if="indexedFields.length"
|
||||
ng-show="indexedFields.length"
|
||||
auto-select-if-only-one="indexedFields"
|
||||
ng-options="field as field.displayName group by field.type for field in indexedFields"
|
||||
ng-change="aggParam.onChange(agg)">
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<input ng-model="agg.params.filters.length" name="filterLength" required min="1" type="number" class="ng-hide">
|
||||
<input ng-model="agg.params.filters.length" name="filterLength" required min="1" type="number" class="ng-hide" />
|
||||
<div class="hintbox" ng-show="aggForm.filterLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one filter
|
||||
<strong>Required:</strong> You must specify at least one filter.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</p>
|
||||
|
||||
<div ng-show="agg.params.ipRangeType != 'mask'">
|
||||
<table class="vis-editor-agg-editor-ranges form-group">
|
||||
<table class="vis-editor-agg-editor-ranges form-group" ng-show="agg.params.ranges.fromTo.length">
|
||||
<tr>
|
||||
<th>
|
||||
<label>From</label>
|
||||
|
@ -43,6 +43,14 @@
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<input ng-if="agg.params.ipRangeType != 'mask'" ng-model="agg.params.ranges.fromTo.length" name="rangeLength" required min="1" type="number" class="ng-hide" />
|
||||
<div class="hintbox" ng-show="aggForm.rangeLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one IP range.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click="agg.params.ranges.fromTo.push({})"
|
||||
class="sidebar-item-button primary">
|
||||
|
@ -51,7 +59,7 @@
|
|||
</div>
|
||||
|
||||
<div ng-show="agg.params.ipRangeType == 'mask'">
|
||||
<table class="vis-editor-agg-editor-ranges form-group">
|
||||
<table class="vis-editor-agg-editor-ranges form-group" ng-show="agg.params.ranges.mask.length">
|
||||
<tr>
|
||||
<th>
|
||||
<label>Mask</label>
|
||||
|
@ -79,6 +87,14 @@
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<input ng-if="agg.params.ipRangeType == 'mask'" ng-model="agg.params.ranges.mask.length" name="rangeLength" required min="1" type="number" class="ng-hide" />
|
||||
<div class="hintbox" ng-show="aggForm.rangeLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one IP range.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click="agg.params.ranges.mask.push({})"
|
||||
class="sidebar-item-button primary">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="vis-editor-agg-form-row">
|
||||
<div class="form-group">
|
||||
<div class="vis-editor-agg-form-row" ng-controller="agg.type.params.byName.precision.controller">
|
||||
<div ng-if="!agg.params.autoPrecision" class="form-group">
|
||||
<label>Precision</label>
|
||||
<div class="vis-editor-agg-form-row">
|
||||
<input
|
||||
|
@ -16,4 +16,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vis-option-item">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
name="autoPrecision"
|
||||
ng-model="agg.params.autoPrecision">
|
||||
Change precision on map zoom
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<table class="vis-editor-agg-editor-ranges form-group">
|
||||
<table class="vis-editor-agg-editor-ranges form-group" ng-show="agg.params.ranges.length">
|
||||
<tr>
|
||||
<th>
|
||||
<label>From</label>
|
||||
|
@ -37,8 +37,16 @@
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<input ng-model="agg.params.ranges.length" name="rangeLength" required min="1" type="number" class="ng-hide" />
|
||||
<div class="hintbox" ng-show="aggForm.rangeLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one range.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click="agg.params.ranges.push({})"
|
||||
class="sidebar-item-button primary">
|
||||
Add Range
|
||||
</div>
|
||||
</div>
|
|
@ -118,9 +118,10 @@ define(function (require) {
|
|||
};
|
||||
|
||||
// Listen for refreshInterval changes
|
||||
$rootScope.$watch('timefilter.refreshInterval', function () {
|
||||
$rootScope.$watchCollection('timefilter.refreshInterval', function () {
|
||||
var refreshValue = _.deepGet($rootScope, 'timefilter.refreshInterval.value');
|
||||
if (_.isNumber(refreshValue)) {
|
||||
var refreshPause = _.deepGet($rootScope, 'timefilter.refreshInterval.pause');
|
||||
if (_.isNumber(refreshValue) && !refreshPause) {
|
||||
self.fetchInterval(refreshValue);
|
||||
} else {
|
||||
self.fetchInterval(0);
|
||||
|
|
|
@ -9,6 +9,7 @@ doc-viewer .doc-viewer {
|
|||
}
|
||||
|
||||
&-value, pre {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<field-format-editor-numeral></field-format-editor-numeral>
|
|
@ -7,7 +7,6 @@ define(function (require) {
|
|||
return Numeral.factory({
|
||||
id: 'percent',
|
||||
title: 'Percentage',
|
||||
editorTemplate: require('text!components/stringify/editors/_numeral.html'),
|
||||
paramDefaults: new BoundToConfigObj({
|
||||
pattern: '=format:percent:defaultPattern',
|
||||
fractional: true
|
||||
|
|
21
src/kibana/components/timefilter/lib/diff_interval.js
Normal file
21
src/kibana/components/timefilter/lib/diff_interval.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
return function diffTimeProvider(Private) {
|
||||
var diff = Private(require('utils/diff_time_picker_vals'));
|
||||
|
||||
return function (self) {
|
||||
var oldRefreshInterval = _.clone(self.refreshInterval);
|
||||
|
||||
return function () {
|
||||
if (diff(self.refreshInterval, oldRefreshInterval)) {
|
||||
self.emit('update');
|
||||
if (!self.refreshInterval.pause && self.refreshInterval.value !== 0) {
|
||||
self.emit('fetch');
|
||||
}
|
||||
}
|
||||
|
||||
oldRefreshInterval = _.clone(self.refreshInterval);
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
17
src/kibana/components/timefilter/lib/diff_time.js
Normal file
17
src/kibana/components/timefilter/lib/diff_time.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
return function diffTimeProvider(Private) {
|
||||
var diff = Private(require('utils/diff_time_picker_vals'));
|
||||
|
||||
return function (self) {
|
||||
var oldTime = _.clone(self.time);
|
||||
return function () {
|
||||
if (diff(self.time, oldTime)) {
|
||||
self.emit('update');
|
||||
self.emit('fetch');
|
||||
}
|
||||
oldTime = _.clone(self.time);
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
define(function (require) {
|
||||
define(function (require) {
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.service('timefilter', function (Private, globalState, $rootScope) {
|
||||
|
@ -22,6 +22,8 @@ define(function (require) {
|
|||
Timefilter.Super.call(this);
|
||||
|
||||
var self = this;
|
||||
var diffTime = Private(require('components/timefilter/lib/diff_time'))(self);
|
||||
var diffInterval = Private(require('components/timefilter/lib/diff_interval'))(self);
|
||||
|
||||
self.enabled = false;
|
||||
|
||||
|
@ -32,6 +34,7 @@ define(function (require) {
|
|||
|
||||
var refreshIntervalDefaults = {
|
||||
display: 'Off',
|
||||
pause: false,
|
||||
section: 0,
|
||||
value: 0
|
||||
};
|
||||
|
@ -55,26 +58,19 @@ define(function (require) {
|
|||
});
|
||||
|
||||
$rootScope.$$timefilter = self;
|
||||
|
||||
$rootScope.$watchMulti([
|
||||
'$$timefilter.time',
|
||||
'$$timefilter.time.from',
|
||||
'$$timefilter.time.to',
|
||||
'$$timefilter.time.mode',
|
||||
'$$timefilter.time',
|
||||
'$$timefilter.time.mode'
|
||||
], diffTime);
|
||||
|
||||
$rootScope.$watchMulti([
|
||||
'$$timefilter.refreshInterval',
|
||||
'$$timefilter.refreshInterval.pause',
|
||||
'$$timefilter.refreshInterval.value'
|
||||
], (function () {
|
||||
var oldTime;
|
||||
var oldRefreshInterval;
|
||||
|
||||
return function () {
|
||||
if (diff(self.time, oldTime) || diff(self.refreshInterval, oldRefreshInterval)) {
|
||||
self.emit('update');
|
||||
}
|
||||
|
||||
oldTime = _.clone(self.time);
|
||||
oldRefreshInterval = _.clone(self.refreshInterval);
|
||||
};
|
||||
}()));
|
||||
], diffInterval);
|
||||
}
|
||||
|
||||
Timefilter.prototype.get = function (indexPattern) {
|
|
@ -19,30 +19,21 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" ng-init="tab = 'filter'">
|
||||
<li ng-class="{active:tab== 'filter'}">
|
||||
<a href ng-click="tab = 'filter'">Time Filter</a>
|
||||
</li>
|
||||
<li ng-class="{active:tab== 'interval'}">
|
||||
<a href ng-click="tab = 'interval'">Refresh Interval</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
<!-- Filters -->
|
||||
<div ng-show="tab == 'filter'" role="tabpanel" class="tab-pane active">
|
||||
<div ng-show="activeTab === 'filter'" role="tabpanel" class="tab-pane active">
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<ul class="nav nav-pills nav-stacked kbn-timepicker-modes">
|
||||
<li ng-class="{active: mode=='quick'}">
|
||||
<li ng-class="{active: mode === 'quick' }">
|
||||
<a ng-click="setMode('quick')">quick</a>
|
||||
</li>
|
||||
<li ng-class="{active: mode=='relative'}">
|
||||
<li ng-class="{active: mode === 'relative' }">
|
||||
<a ng-click="setMode('relative')">relative</a>
|
||||
</li>
|
||||
<li ng-class="{active: mode=='absolute'}">
|
||||
<li ng-class="{active: mode === 'absolute' }">
|
||||
<a ng-click="setMode('absolute')">absolute</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -167,12 +158,12 @@
|
|||
</div>
|
||||
|
||||
<!-- Refresh Intervals -->
|
||||
<div ng-show="tab == 'interval'" role="tabpanel" class="tab-pane active">
|
||||
<div ng-show="activeTab === 'interval'" role="tabpanel" class="tab-pane active">
|
||||
<br>
|
||||
<div ng-repeat="list in refreshLists" class="kbn-refresh-section">
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="inter in list">
|
||||
<a class="refresh-interval" ng-class="{ 'refresh-interval-active': interval.value == inter.value }" ng-click="setRefreshInterval(inter)">
|
||||
<a class="refresh-interval" ng-class="{ 'refresh-interval-active': interval.value === inter.value }" ng-click="setRefreshInterval(inter)">
|
||||
{{inter.display}}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -6,7 +6,7 @@ define(function (require) {
|
|||
var moment = require('moment');
|
||||
|
||||
require('directives/input_datetime');
|
||||
require('directives/greater_than');
|
||||
require('directives/inequality');
|
||||
require('components/timepicker/quick_ranges');
|
||||
require('components/timepicker/refresh_intervals');
|
||||
require('components/timepicker/time_units');
|
||||
|
@ -18,7 +18,8 @@ define(function (require) {
|
|||
from: '=',
|
||||
to: '=',
|
||||
mode: '=',
|
||||
interval: '='
|
||||
interval: '=',
|
||||
activeTab: '='
|
||||
},
|
||||
template: html,
|
||||
controller: function ($scope) {
|
||||
|
@ -28,6 +29,8 @@ define(function (require) {
|
|||
|
||||
$scope.format = 'MMMM Do YYYY, HH:mm:ss.SSS';
|
||||
$scope.modes = ['quick', 'relative', 'absolute'];
|
||||
$scope.activeTab = $scope.activeTab || 'filter';
|
||||
|
||||
if (_.isUndefined($scope.mode)) $scope.mode = 'quick';
|
||||
|
||||
$scope.quickLists = _(quickRanges).groupBy('section').values().value();
|
||||
|
@ -137,6 +140,11 @@ define(function (require) {
|
|||
};
|
||||
|
||||
$scope.setRefreshInterval = function (interval) {
|
||||
interval = _.clone(interval);
|
||||
console.log('before: ' + interval.pause);
|
||||
interval.pause = (interval.pause == null || interval.pause === false) ? false : true;
|
||||
|
||||
console.log('after: ' + interval.pause);
|
||||
$scope.interval = interval;
|
||||
};
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ define(function (require) {
|
|||
if (_.isString(this.type)) this.type = visTypes.byName[this.type];
|
||||
|
||||
this.listeners = _.assign({}, state.listeners, this.type.listeners);
|
||||
this.params = _.defaults({}, state.params || {}, this.type.params.defaults || {});
|
||||
this.params = _.defaults({}, _.cloneDeep(state.params || {}), this.type.params.defaults || {});
|
||||
|
||||
this.aggs = new AggConfigs(this, state.aggs);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,9 @@ define(function (require) {
|
|||
opts = opts || {};
|
||||
|
||||
return function (vis) {
|
||||
var isUserDefinedYAxis = vis._attr.setYExtents;
|
||||
var data;
|
||||
|
||||
if (opts.zeroFill) {
|
||||
data = new Data(injectZeros(vis.data), vis._attr);
|
||||
} else {
|
||||
|
@ -41,12 +43,13 @@ define(function (require) {
|
|||
alerts: new Alerts(vis, data, opts.alerts),
|
||||
yAxis: new YAxis({
|
||||
el : vis.el,
|
||||
yMin : data.getYMin(),
|
||||
yMax : data.getYMax(),
|
||||
_attr: vis._attr,
|
||||
yAxisFormatter: data.get('yAxisFormatter')
|
||||
yMin : isUserDefinedYAxis ? vis._attr.yAxis.min : data.getYMin(),
|
||||
yMax : isUserDefinedYAxis ? vis._attr.yAxis.max : data.getYMax(),
|
||||
yAxisFormatter: data.get('yAxisFormatter'),
|
||||
_attr: vis._attr
|
||||
})
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -79,4 +82,3 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ define(function (require) {
|
|||
*/
|
||||
function YAxis(args) {
|
||||
this.el = args.el;
|
||||
this.yMin = args.yMin;
|
||||
this.yMax = args.yMax;
|
||||
this.scale = null;
|
||||
this.domain = [args.yMin, args.yMax];
|
||||
this.yAxisFormatter = args.yAxisFormatter;
|
||||
this._attr = args._attr || {};
|
||||
}
|
||||
|
@ -33,29 +33,59 @@ define(function (require) {
|
|||
d3.select(this.el).selectAll('.y-axis-div').call(this.draw());
|
||||
};
|
||||
|
||||
YAxis.prototype.throwCustomError = function (message) {
|
||||
YAxis.prototype._isPercentage = function () {
|
||||
return (this._attr.mode === 'percentage');
|
||||
};
|
||||
|
||||
YAxis.prototype._isUserDefined = function () {
|
||||
return (this._attr.setYExtents);
|
||||
};
|
||||
|
||||
YAxis.prototype._isYExtents = function () {
|
||||
return (this._attr.defaultYExtents);
|
||||
};
|
||||
|
||||
YAxis.prototype._validateUserExtents = function (domain) {
|
||||
var self = this;
|
||||
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
YAxis.prototype._getExtents = function (domain) {
|
||||
var min = domain[0];
|
||||
var max = domain[1];
|
||||
|
||||
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)];
|
||||
if (this._isUserDefined()) return this._validateUserExtents(domain);
|
||||
return domain;
|
||||
};
|
||||
|
||||
YAxis.prototype._throwCustomError = function (message) {
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
YAxis.prototype.throwCannotLogScaleNegVals = function () {
|
||||
YAxis.prototype._throwCannotLogScaleNegVals = function () {
|
||||
throw new errors.CannotLogScaleNegVals();
|
||||
};
|
||||
|
||||
YAxis.prototype.throwNoResultsError = function () {
|
||||
throw new errors.NoResults();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the appropriate D3 scale
|
||||
*
|
||||
* @param fnName {String} D3 scale
|
||||
* @returns {*}
|
||||
*/
|
||||
YAxis.prototype.getScaleType = function (fnName) {
|
||||
YAxis.prototype._getScaleType = function (fnName) {
|
||||
if (fnName === 'square root') fnName = 'sqrt'; // Rename 'square root' to 'sqrt'
|
||||
fnName = fnName || 'linear';
|
||||
|
||||
if (typeof d3.scale[fnName] !== 'function') return this.throwCustomError('YAxis.getScaleType: ' + fnName + ' is not a function');
|
||||
if (typeof d3.scale[fnName] !== 'function') return this._throwCustomError('YAxis.getScaleType: ' + fnName + ' is not a function');
|
||||
|
||||
return d3.scale[fnName]();
|
||||
};
|
||||
|
@ -69,29 +99,9 @@ define(function (require) {
|
|||
* @param yMax
|
||||
* @returns {*[]}
|
||||
*/
|
||||
YAxis.prototype.returnLogDomain = function (yMin, yMax) {
|
||||
if (yMin < 0 || yMax < 0) return this.throwCannotLogScaleNegVals();
|
||||
return [Math.max(1, yMin), yMax];
|
||||
};
|
||||
|
||||
YAxis.prototype._setDefaultYExtents = function () {
|
||||
return this._attr.defaultYExtents;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the domain, i.e. the extent of the y axis
|
||||
*
|
||||
* @param scale {String} Kibana scale
|
||||
* @param yMin {Number} Y-axis minimum value
|
||||
* @param yMax {Number} Y-axis maximum value
|
||||
* @returns {*[]}
|
||||
*/
|
||||
YAxis.prototype.getDomain = function (scale, yMin, yMax) {
|
||||
if (this._setDefaultYExtents()) return [yMin, yMax];
|
||||
if (scale === 'log') return this.returnLogDomain(yMin, yMax); // Negative values cannot be displayed with a log scale.
|
||||
if (yMin === 0 && yMax === 0) return this.throwNoResultsError(); // yMin and yMax can never both be equal to zero
|
||||
|
||||
return [Math.min(0, yMin), Math.max(0, yMax)];
|
||||
YAxis.prototype._logDomain = function (min, max) {
|
||||
if (min < 0 || max < 0) return this._throwCannotLogScaleNegVals();
|
||||
return [Math.max(1, min), max];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -102,14 +112,16 @@ define(function (require) {
|
|||
* @returns {D3.Scale.QuantitiveScale|*} D3 yScale function
|
||||
*/
|
||||
YAxis.prototype.getYScale = function (height) {
|
||||
this.yScale = this.getScaleType(this._attr.scale)
|
||||
.domain(this.getDomain(this._attr.scale, this.yMin, this.yMax))
|
||||
var scale = this._getScaleType(this._attr.scale);
|
||||
var domain = this._getExtents(this.domain);
|
||||
|
||||
this.yScale = scale
|
||||
.domain(domain)
|
||||
.range([height, 0]);
|
||||
|
||||
// Nicing the scale, rounds values down or up to make the scale look better
|
||||
// When defaultYExtents are selected, the extents (i.e. min and max) should
|
||||
// be shown without any rounding.
|
||||
if (!this._attr.defaultYExtents) return this.yScale.nice();
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -120,6 +132,10 @@ define(function (require) {
|
|||
return d3.format('n');
|
||||
};
|
||||
|
||||
YAxis.prototype._validateYScale = function (yScale) {
|
||||
if (!yScale || _.isNaN(yScale)) throw new Error('yScale is ' + yScale);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the d3 y axis function
|
||||
*
|
||||
|
@ -129,16 +145,12 @@ define(function (require) {
|
|||
*/
|
||||
YAxis.prototype.getYAxis = function (height) {
|
||||
var yScale = this.getYScale(height);
|
||||
|
||||
// y scale should never be `NaN`
|
||||
if (!yScale || _.isNaN(yScale)) {
|
||||
throw new Error('yScale is ' + yScale);
|
||||
}
|
||||
this._validateYScale(yScale);
|
||||
|
||||
// Create the d3 yAxis function
|
||||
this.yAxis = d3.svg.axis()
|
||||
.scale(yScale)
|
||||
.tickFormat(this.tickFormat())
|
||||
.tickFormat(this.tickFormat(this.domain))
|
||||
.ticks(this.tickScale(height))
|
||||
.orient('left');
|
||||
|
||||
|
@ -177,7 +189,6 @@ define(function (require) {
|
|||
var isWiggleOrSilhouette = (mode === 'wiggle' || mode === 'silhouette');
|
||||
|
||||
return function (selection) {
|
||||
|
||||
selection.each(function () {
|
||||
var el = this;
|
||||
|
||||
|
|
|
@ -76,19 +76,56 @@
|
|||
|
||||
.leaflet-popup {
|
||||
margin-bottom: 16px !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper {
|
||||
background: rgba(70, 82, 93, 0.95) !important;
|
||||
color: @gray-lighter !important;
|
||||
background: @tooltip-bg !important;
|
||||
color: @tooltip-color !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.leaflet-popup-content {
|
||||
padding: 8px !important;
|
||||
margin: 0 !important;
|
||||
line-height: 14px !important;
|
||||
line-height: 1.1 !important;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
> :last-child {
|
||||
margin-bottom: @tooltip-space;
|
||||
}
|
||||
|
||||
> * {
|
||||
margin: @tooltip-space @tooltip-space 0;
|
||||
}
|
||||
|
||||
table {
|
||||
td,th {
|
||||
padding: @tooltip-space-tight;
|
||||
|
||||
&.row-bucket {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a header, give it a border that matches
|
||||
// those in the body
|
||||
thead tr {
|
||||
border-bottom: 1px solid @gray;
|
||||
}
|
||||
|
||||
// only apply to tr in the body, not the header
|
||||
tbody tr {
|
||||
border-top: 1px solid @gray;
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.leaflet-popup-tip-container, .leaflet-popup-close-button {
|
||||
|
@ -109,7 +146,7 @@
|
|||
}
|
||||
|
||||
.leaflet-draw-tooltip {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* filter to desaturate mapquest tiles */
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
define(function (require) {
|
||||
return function TileMapFactory(d3, Private) {
|
||||
return function TileMapFactory(d3, Private, config) {
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var L = require('leaflet');
|
||||
require('leaflet-heat');
|
||||
require('leaflet-draw');
|
||||
|
||||
var Dispatch = Private(require('components/vislib/lib/dispatch'));
|
||||
var Chart = Private(require('components/vislib/visualizations/_chart'));
|
||||
var errors = require('errors');
|
||||
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
var mapCenter = [15, 5];
|
||||
var mapZoom = 2;
|
||||
var defaultMapCenter = [15, 5];
|
||||
var defaultMapZoom = 2;
|
||||
|
||||
/**
|
||||
* Tile Map Visualization: renders maps
|
||||
|
@ -29,11 +29,14 @@ define(function (require) {
|
|||
if (!(this instanceof TileMap)) {
|
||||
return new TileMap(handler, chartEl, chartData);
|
||||
}
|
||||
|
||||
TileMap.Super.apply(this, arguments);
|
||||
|
||||
// track the map objects
|
||||
this.maps = [];
|
||||
|
||||
this.tooltipFormatter = chartData.tooltipFormatter;
|
||||
|
||||
this.events = new Dispatch(handler);
|
||||
|
||||
// add allmin and allmax to geoJson
|
||||
|
@ -55,26 +58,23 @@ define(function (require) {
|
|||
// clean up old maps
|
||||
self.destroy();
|
||||
|
||||
// create a new maps array
|
||||
// clear maps array
|
||||
self.maps = [];
|
||||
self.popups = [];
|
||||
|
||||
var worldBounds = L.latLngBounds([-90, -220], [90, 220]);
|
||||
|
||||
return function (selection) {
|
||||
|
||||
self._attr.mapZoom = self._attr.mapZoom || defaultMapZoom;
|
||||
self._attr.mapCenter = self._attr.mapCenter || defaultMapCenter;
|
||||
|
||||
selection.each(function (data) {
|
||||
|
||||
if (self._attr.mapZoom) {
|
||||
mapZoom = self._attr.mapZoom;
|
||||
}
|
||||
if (self._attr.mapCenter) {
|
||||
mapCenter = self._attr.mapCenter;
|
||||
}
|
||||
// add leaflet latLngs to properties for tooltip
|
||||
var mapData = self.addLatLng(data.geoJson);
|
||||
|
||||
var mapData = data.geoJson;
|
||||
var div = $(this).addClass('tilemap');
|
||||
|
||||
var featureLayer;
|
||||
var tileLayer = L.tileLayer('https://otile{s}-s.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg', {
|
||||
attribution: 'Tiles by <a href="http://www.mapquest.com/">MapQuest</a> — ' +
|
||||
'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
|
||||
|
@ -82,7 +82,6 @@ define(function (require) {
|
|||
subdomains: '1234'
|
||||
});
|
||||
|
||||
|
||||
var drawOptions = {draw: {}};
|
||||
_.each(['polyline', 'polygon', 'circle', 'marker', 'rectangle'], function (drawShape) {
|
||||
if (!self.events.listenerCount(drawShape)) {
|
||||
|
@ -101,8 +100,8 @@ define(function (require) {
|
|||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
layers: tileLayer,
|
||||
center: mapCenter,
|
||||
zoom: mapZoom,
|
||||
center: self._attr.mapCenter,
|
||||
zoom: self._attr.mapZoom,
|
||||
noWrap: true,
|
||||
maxBounds: worldBounds,
|
||||
scrollWheelZoom: false,
|
||||
|
@ -111,24 +110,28 @@ define(function (require) {
|
|||
|
||||
var map = L.map(div[0], mapOptions);
|
||||
|
||||
var featureLayer = self.markerType(map, mapData).addTo(map);
|
||||
|
||||
if (data.geoJson.features.length) {
|
||||
map.addControl(new L.Control.Draw(drawOptions));
|
||||
}
|
||||
|
||||
tileLayer.on('tileload', function () {
|
||||
function saturateTiles() {
|
||||
self.saturateTiles();
|
||||
});
|
||||
}
|
||||
|
||||
featureLayer = self.markerType(map, mapData).addTo(map);
|
||||
tileLayer.on('tileload', saturateTiles);
|
||||
|
||||
map.on('unload', function () {
|
||||
tileLayer.off('tileload', self.saturateTiles);
|
||||
tileLayer.off('tileload', saturateTiles);
|
||||
});
|
||||
|
||||
map.on('moveend', function setZoomCenter() {
|
||||
mapZoom = self._attr.mapZoom = map.getZoom();
|
||||
mapCenter = self._attr.mapCenter = map.getCenter();
|
||||
featureLayer.clearLayers();
|
||||
self._attr.mapZoom = map.getZoom();
|
||||
self._attr.mapCenter = map.getCenter();
|
||||
|
||||
map.removeLayer(featureLayer);
|
||||
|
||||
featureLayer = self.markerType(map, mapData).addTo(map);
|
||||
});
|
||||
|
||||
|
@ -155,14 +158,21 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
map.on('zoomend', function () {
|
||||
self.events.emit('mapZoomEnd', {
|
||||
data: mapData,
|
||||
zoom: map.getZoom()
|
||||
});
|
||||
});
|
||||
|
||||
// add label for splits
|
||||
if (mapData.properties.label) {
|
||||
self.addLabel(mapData.properties.label, map);
|
||||
}
|
||||
|
||||
var fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-zoom leaflet-control-fit');
|
||||
|
||||
if (mapData && mapData.features.length > 0) {
|
||||
var fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-zoom leaflet-control-fit');
|
||||
|
||||
// Add button to fit container to points
|
||||
var FitControl = L.Control.extend({
|
||||
options: {
|
||||
|
@ -191,15 +201,19 @@ define(function (require) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Return features within the map bounds
|
||||
* return whether feature is within map bounds
|
||||
*
|
||||
* @method _filterToMapBounds
|
||||
* @param map {Leaflet Object}
|
||||
* @return {boolean}
|
||||
*/
|
||||
TileMap.prototype._filterToMapBounds = function (map) {
|
||||
function cloneAndReverse(arr) { return _(_.clone(arr)).reverse().value(); }
|
||||
return function (feature) {
|
||||
var coordinates = feature.geometry.coordinates;
|
||||
var p0 = coordinates[0];
|
||||
var p1 = coordinates[1];
|
||||
var mapBounds = map.getBounds();
|
||||
var bucketRectBounds = feature.properties.rectangle.map(cloneAndReverse);
|
||||
|
||||
return map.getBounds().contains([p1, p0]);
|
||||
return mapBounds.intersects(bucketRectBounds);
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -237,20 +251,35 @@ define(function (require) {
|
|||
};
|
||||
|
||||
/**
|
||||
* zoom map to fit all features in featureLayer
|
||||
* add Leaflet latLng to mapData properties
|
||||
*
|
||||
* @method fitBounds
|
||||
* @param map {Object}
|
||||
* @param featureLayer {Leaflet object}
|
||||
* @return {Leaflet object} featureLayer
|
||||
* @method addLatLng
|
||||
* @param mapData {geoJson Object}
|
||||
* @return mapData {geoJson Object}
|
||||
*/
|
||||
TileMap.prototype.fitBounds = function (map, featureLayer) {
|
||||
TileMap.prototype.addLatLng = function (mapData) {
|
||||
for (var i = 0; i < mapData.features.length; i++) {
|
||||
var latLng = L.latLng(mapData.features[i].geometry.coordinates[1], mapData.features[i].geometry.coordinates[0]);
|
||||
mapData.features[i].properties.latLng = latLng;
|
||||
}
|
||||
|
||||
map.fitBounds(featureLayer.getBounds());
|
||||
return mapData;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove css class on map tiles
|
||||
* zoom map to fit all features in featureLayer
|
||||
*
|
||||
* @method fitBounds
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return {undefined}
|
||||
*/
|
||||
TileMap.prototype.fitBounds = function (map, mapData) {
|
||||
map.fitBounds(mapData._latlngs || mapData.getBounds());
|
||||
};
|
||||
|
||||
/**
|
||||
* remove css class for desat filters on map tiles
|
||||
*
|
||||
* @method saturateTiles
|
||||
* @return {Leaflet object} featureLayer
|
||||
|
@ -261,17 +290,119 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds nearest feature in mapData to event latlng
|
||||
*
|
||||
* @method nearestFeature
|
||||
* @param point {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return nearestPoint {Leaflet Object}
|
||||
*/
|
||||
TileMap.prototype.nearestFeature = function (point, mapData) {
|
||||
var self = this;
|
||||
var distance = Infinity;
|
||||
var nearest;
|
||||
|
||||
if (point.lng < -180 || point.lng > 180) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < mapData.features.length; i++) {
|
||||
var dist = point.distanceTo(mapData.features[i].properties.latLng);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
nearest = mapData.features[i];
|
||||
}
|
||||
}
|
||||
nearest.properties.eventDistance = distance;
|
||||
|
||||
return nearest;
|
||||
};
|
||||
|
||||
/**
|
||||
* display tooltip if feature is close enough to event latlng
|
||||
*
|
||||
* @method tooltipProximity
|
||||
* @param latlng {Leaflet Object}
|
||||
* @param zoom {Number}
|
||||
* @param feature {geoJson Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @return boolean
|
||||
*/
|
||||
TileMap.prototype.tooltipProximity = function (latlng, zoom, feature, map) {
|
||||
if (!feature) {
|
||||
return;
|
||||
}
|
||||
|
||||
var showTip = false;
|
||||
|
||||
// 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
|
||||
var zoomScale = d3.scale.linear()
|
||||
.domain([1, 4, 7, 10, 13, 16, 18])
|
||||
.range([1000000, 300000, 100000, 15000, 2000, 150, 50]);
|
||||
|
||||
var proximity = zoomScale(zoom);
|
||||
var distance = latlng.distanceTo(feature.properties.latLng);
|
||||
|
||||
// maxLngDif is max difference in longitudes
|
||||
// to prevent feature tooltip from appearing 360°
|
||||
// away from event latlng
|
||||
var maxLngDif = 40;
|
||||
var lngDif = Math.abs(latlng.lng - feature.properties.latLng.lng);
|
||||
|
||||
if (distance < proximity && lngDif < maxLngDif) {
|
||||
showTip = true;
|
||||
}
|
||||
|
||||
delete feature.properties.eventDistance;
|
||||
|
||||
var testScale = d3.scale.pow().exponent(0.2)
|
||||
.domain([1, 18])
|
||||
.range([1500000, 50]);
|
||||
return showTip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if event latlng is within bounds of mapData
|
||||
* features and shows tooltip for that feature
|
||||
*
|
||||
* @method showTooltip
|
||||
* @param e {Event}
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return {undefined}
|
||||
*/
|
||||
TileMap.prototype.showTooltip = function (map, feature) {
|
||||
var content = this.tooltipFormatter(feature);
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lat = feature.geometry.coordinates[1];
|
||||
var lng = feature.geometry.coordinates[0];
|
||||
var latLng = L.latLng(lat, lng);
|
||||
|
||||
L.popup({autoPan: false})
|
||||
.setLatLng(latLng)
|
||||
.setContent(content)
|
||||
.openOn(map);
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch type of data overlay for map:
|
||||
* creates featurelayer from mapData (geoJson)
|
||||
*
|
||||
* @method markerType
|
||||
* @param map {Object}
|
||||
* @param mapData {Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return {Leaflet object} featureLayer
|
||||
*/
|
||||
TileMap.prototype.markerType = function (map, mapData) {
|
||||
var featureLayer;
|
||||
|
||||
if (mapData) {
|
||||
if (this._attr.mapType === 'Scaled Circle Markers') {
|
||||
featureLayer = this.scaledCircleMarkers(map, mapData);
|
||||
|
@ -279,6 +410,8 @@ define(function (require) {
|
|||
featureLayer = this.shadedCircleMarkers(map, mapData);
|
||||
} else if (this._attr.mapType === 'Shaded Geohash Grid') {
|
||||
featureLayer = this.shadedGeohashGrid(map, mapData);
|
||||
} else if (this._attr.mapType === 'Heatmap') {
|
||||
featureLayer = this.heatMap(map, mapData);
|
||||
} else {
|
||||
featureLayer = this.scaledCircleMarkers(map, mapData);
|
||||
}
|
||||
|
@ -293,8 +426,8 @@ define(function (require) {
|
|||
* with circle markers that are scaled to illustrate values
|
||||
*
|
||||
* @method scaledCircleMarkers
|
||||
* @param map {Object}
|
||||
* @param mapData {Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return {Leaflet object} featureLayer
|
||||
*/
|
||||
TileMap.prototype.scaledCircleMarkers = function (map, mapData) {
|
||||
|
@ -303,17 +436,22 @@ define(function (require) {
|
|||
// super min and max from all chart data
|
||||
var min = mapData.properties.allmin;
|
||||
var max = mapData.properties.allmax;
|
||||
var zoom = map.getZoom();
|
||||
var precision = mapData.properties.precision;
|
||||
|
||||
// multiplier to reduce size of all circles
|
||||
var scaleFactor = 0.6;
|
||||
|
||||
var radiusScaler = 2.5;
|
||||
|
||||
var featureLayer = L.geoJson(mapData, {
|
||||
pointToLayer: function (feature, latlng) {
|
||||
var count = feature.properties.count;
|
||||
var scaledRadius = self.radiusScale(count, max, feature) * 2;
|
||||
return L.circle(latlng, scaledRadius);
|
||||
var scaledRadius = self.radiusScale(count, max, zoom, precision) * scaleFactor;
|
||||
return L.circleMarker(latlng).setRadius(scaledRadius);
|
||||
},
|
||||
onEachFeature: function (feature, layer) {
|
||||
self.bindPopup(feature, layer);
|
||||
self.bindPopup(feature, layer, map);
|
||||
},
|
||||
style: function (feature) {
|
||||
return self.applyShadingStyle(feature, min, max);
|
||||
|
@ -335,8 +473,8 @@ define(function (require) {
|
|||
* with circle markers that are shaded to illustrate values
|
||||
*
|
||||
* @method shadedCircleMarkers
|
||||
* @param map {Object}
|
||||
* @param mapData {Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return {Leaflet object} featureLayer
|
||||
*/
|
||||
TileMap.prototype.shadedCircleMarkers = function (map, mapData) {
|
||||
|
@ -346,14 +484,17 @@ define(function (require) {
|
|||
var min = mapData.properties.allmin;
|
||||
var max = mapData.properties.allmax;
|
||||
|
||||
// multiplier to reduce size of all circles
|
||||
var scaleFactor = 0.8;
|
||||
|
||||
var featureLayer = L.geoJson(mapData, {
|
||||
pointToLayer: function (feature, latlng) {
|
||||
var count = feature.properties.count;
|
||||
var radius = self.geohashMinDistance(feature);
|
||||
var radius = self.geohashMinDistance(feature) * scaleFactor;
|
||||
return L.circle(latlng, radius);
|
||||
},
|
||||
onEachFeature: function (feature, layer) {
|
||||
self.bindPopup(feature, layer);
|
||||
self.bindPopup(feature, layer, map);
|
||||
},
|
||||
style: function (feature) {
|
||||
return self.applyShadingStyle(feature, min, max);
|
||||
|
@ -375,8 +516,8 @@ define(function (require) {
|
|||
* with rectangles that show the geohash grid bounds
|
||||
*
|
||||
* @method geohashGrid
|
||||
* @param map {Object}
|
||||
* @param mapData {Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return {undefined}
|
||||
*/
|
||||
TileMap.prototype.shadedGeohashGrid = function (map, mapData) {
|
||||
|
@ -392,15 +533,15 @@ define(function (require) {
|
|||
pointToLayer: function (feature, latlng) {
|
||||
var geohashRect = feature.properties.rectangle;
|
||||
// get bounds from northEast[3] and southWest[1]
|
||||
// points in geohash rectangle
|
||||
var bounds = [
|
||||
// corners in geohash rectangle
|
||||
var corners = [
|
||||
[geohashRect[3][1], geohashRect[3][0]],
|
||||
[geohashRect[1][1], geohashRect[1][0]]
|
||||
];
|
||||
return L.rectangle(bounds);
|
||||
return L.rectangle(corners);
|
||||
},
|
||||
onEachFeature: function (feature, layer) {
|
||||
self.bindPopup(feature, layer);
|
||||
self.bindPopup(feature, layer, map);
|
||||
layer.on({
|
||||
mouseover: function (e) {
|
||||
var layer = e.target;
|
||||
|
@ -425,12 +566,79 @@ define(function (require) {
|
|||
return featureLayer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of data overlay for map:
|
||||
* creates canvas layer from mapData (geoJson)
|
||||
* with leaflet.heat plugin
|
||||
*
|
||||
* @method heatMap
|
||||
* @param map {Leaflet Object}
|
||||
* @param mapData {geoJson Object}
|
||||
* @return featureLayer {Leaflet object}
|
||||
*/
|
||||
TileMap.prototype.heatMap = function (map, mapData) {
|
||||
var self = this;
|
||||
var max = mapData.properties.allmax;
|
||||
var points = this.dataToHeatArray(mapData, max);
|
||||
|
||||
var options = {
|
||||
radius: +this._attr.heatRadius,
|
||||
blur: +this._attr.heatBlur,
|
||||
maxZoom: +this._attr.heatMaxZoom,
|
||||
minOpacity: +this._attr.heatMinOpacity
|
||||
};
|
||||
|
||||
var featureLayer = L.heatLayer(points, options);
|
||||
|
||||
if (self._attr.addTooltip && self.tooltipFormatter && !self._attr.disableTooltips) {
|
||||
map.on('mousemove', _.debounce(mouseMoveLocation, 15, {
|
||||
'leading': true,
|
||||
'trailing': false
|
||||
}));
|
||||
map.on('mouseout', function (e) {
|
||||
map.closePopup();
|
||||
});
|
||||
map.on('mousedown', function () {
|
||||
self._attr.disableTooltips = true;
|
||||
map.closePopup();
|
||||
});
|
||||
map.on('mouseup', function () {
|
||||
self._attr.disableTooltips = false;
|
||||
});
|
||||
}
|
||||
|
||||
function mouseMoveLocation(e) {
|
||||
map.closePopup();
|
||||
|
||||
// unhighlight all svgs
|
||||
d3.selectAll('path.geohash', this.chartEl).classed('geohash-hover', false);
|
||||
|
||||
if (!mapData.features.length || self._attr.disableTooltips) {
|
||||
return;
|
||||
}
|
||||
|
||||
var latlng = e.latlng;
|
||||
|
||||
// find nearest feature to event latlng
|
||||
var feature = self.nearestFeature(latlng, mapData);
|
||||
|
||||
var zoom = map.getZoom();
|
||||
|
||||
// show tooltip if close enough to event latlng
|
||||
if (self.tooltipProximity(latlng, zoom, feature, map)) {
|
||||
self.showTooltip(map, feature, latlng);
|
||||
}
|
||||
}
|
||||
|
||||
return featureLayer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds label div to each map when data is split
|
||||
*
|
||||
* @method addLabel
|
||||
* @param mapLabel {String}
|
||||
* @param map {Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @return {undefined}
|
||||
*/
|
||||
TileMap.prototype.addLabel = function (mapLabel, map) {
|
||||
|
@ -452,7 +660,7 @@ define(function (require) {
|
|||
*
|
||||
* @method addLegend
|
||||
* @param data {Object}
|
||||
* @param map {Object}
|
||||
* @param map {Leaflet Object}
|
||||
* @return {undefined}
|
||||
*/
|
||||
TileMap.prototype.addLegend = function (data, map) {
|
||||
|
@ -522,6 +730,7 @@ define(function (require) {
|
|||
* 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 () {
|
||||
|
@ -540,26 +749,54 @@ define(function (require) {
|
|||
* @param layer {Object}
|
||||
* return {undefined}
|
||||
*/
|
||||
TileMap.prototype.bindPopup = function (feature, layer) {
|
||||
var props = feature.properties;
|
||||
var popup = L.popup({
|
||||
className: 'leaflet-popup-kibana',
|
||||
autoPan: false
|
||||
})
|
||||
.setContent(
|
||||
'Geohash: ' + props.geohash + '<br>' +
|
||||
'Center: ' + props.center[1].toFixed(1) + ', ' + props.center[0].toFixed(1) + '<br>' +
|
||||
props.valueLabel + ': ' + props.count
|
||||
);
|
||||
layer.bindPopup(popup)
|
||||
.on('mouseover', function (e) {
|
||||
layer.openPopup();
|
||||
})
|
||||
.on('mouseout', function (e) {
|
||||
layer.closePopup();
|
||||
TileMap.prototype.bindPopup = function (feature, layer, map) {
|
||||
var self = this;
|
||||
var popup = layer.on({
|
||||
mouseover: function (e) {
|
||||
var layer = e.target;
|
||||
// bring layer to front if not older browser
|
||||
if (!L.Browser.ie && !L.Browser.opera) {
|
||||
layer.bringToFront();
|
||||
}
|
||||
var latlng = L.latLng(feature.geometry.coordinates[0], feature.geometry.coordinates[1]);
|
||||
self.showTooltip(map, feature, latlng);
|
||||
},
|
||||
mouseout: function (e) {
|
||||
map.closePopup();
|
||||
}
|
||||
});
|
||||
|
||||
this.popups.push({elem: popup, layer: layer});
|
||||
this.popups.push(popup);
|
||||
};
|
||||
|
||||
/**
|
||||
* retuns data for data for heat map intensity
|
||||
* if heatNormalizeData attribute is checked/true
|
||||
• normalizes data for heat map intensity
|
||||
*
|
||||
* @param mapData {geoJson Object}
|
||||
* @param nax {Number}
|
||||
* @method dataToHeatArray
|
||||
* @return {Array}
|
||||
*/
|
||||
TileMap.prototype.dataToHeatArray = function (mapData, max) {
|
||||
var self = this;
|
||||
|
||||
return mapData.features.map(function (feature) {
|
||||
var lat = feature.geometry.coordinates[1];
|
||||
var lng = feature.geometry.coordinates[0];
|
||||
var heatIntensity;
|
||||
|
||||
if (!self._attr.heatNormalizeData) {
|
||||
// show bucket count on heatmap
|
||||
heatIntensity = feature.properties.count;
|
||||
} else {
|
||||
// show bucket count normalized to max value
|
||||
heatIntensity = parseInt(feature.properties.count / max * 100);
|
||||
}
|
||||
|
||||
return [lat, lng, heatIntensity];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -589,22 +826,33 @@ define(function (require) {
|
|||
|
||||
/**
|
||||
* radiusScale returns a number for scaled circle markers
|
||||
* approx. square root of count
|
||||
* which is multiplied by a factor based on the geohash precision
|
||||
* square root of count / max
|
||||
* multiplied by a value based on map zoom
|
||||
* multiplied by a value based on data precision
|
||||
* for relative sizing of markers
|
||||
*
|
||||
* @method radiusScale
|
||||
* @param count {Number}
|
||||
* @param max {Number}
|
||||
<<<<<<< HEAD
|
||||
* @param feature {Object}
|
||||
=======
|
||||
* @param zoom {Number}
|
||||
* @param precision {Number}
|
||||
>>>>>>> f0414d554915d151e2cdc3501bd3c7fd1889a0a8
|
||||
* @return {Number}
|
||||
*/
|
||||
TileMap.prototype.radiusScale = function (count, max, feature) {
|
||||
TileMap.prototype.radiusScale = function (count, max, zoom, precision) {
|
||||
// exp = 0.5 for square root ratio
|
||||
// exp = 1 for linear ratio
|
||||
var exp = 0.6;
|
||||
var maxr = this.geohashMinDistance(feature);
|
||||
return Math.pow(count, exp) / Math.pow(max, exp) * maxr;
|
||||
var exp = 0.5;
|
||||
var precisionBiasNumerator = 200;
|
||||
var precisionBiasBase = 5;
|
||||
var pct = count / max;
|
||||
var constantZoomRadius = 0.5 * Math.pow(2, zoom);
|
||||
var precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision);
|
||||
|
||||
return Math.pow(pct, exp) * constantZoomRadius * precisionScale;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -662,8 +910,7 @@ define(function (require) {
|
|||
TileMap.prototype.destroy = function () {
|
||||
if (this.popups) {
|
||||
this.popups.forEach(function (popup) {
|
||||
popup.elem.off('mouseover').off('mouseout');
|
||||
popup.layer.unbindPopup(popup.elem);
|
||||
popup.off('mouseover').off('mouseout');
|
||||
});
|
||||
this.popups = [];
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
define(function (require) {
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.directive('greaterThan', function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $el, $attr, ngModel) {
|
||||
var val = $attr.greaterThan || 0;
|
||||
ngModel.$parsers.push(validator);
|
||||
ngModel.$formatters.push(validator);
|
||||
|
||||
function validator(value) {
|
||||
var valid = false;
|
||||
if (!isNaN(value)) valid = value > val;
|
||||
ngModel.$setValidity('greaterThan', valid);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
40
src/kibana/directives/inequality.js
Normal file
40
src/kibana/directives/inequality.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
define(function (require) {
|
||||
|
||||
function makeDirectiveDef(id, compare) {
|
||||
return function ($parse) {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $el, $attr, ngModel) {
|
||||
var getBound = function () { return $parse($attr[id])(); };
|
||||
var defaultVal = {
|
||||
'greaterThan': -Infinity,
|
||||
'lessThan': Infinity
|
||||
}[id];
|
||||
|
||||
ngModel.$parsers.push(validate);
|
||||
ngModel.$formatters.push(validate);
|
||||
|
||||
$scope.$watch(getBound, function () {
|
||||
validate(ngModel.$viewValue);
|
||||
});
|
||||
|
||||
function validate(val) {
|
||||
var bound = !isNaN(getBound()) ? +getBound() : defaultVal;
|
||||
var valid = !isNaN(bound) && !isNaN(val) && compare(val, bound);
|
||||
ngModel.$setValidity(id, valid);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.directive('greaterThan', makeDirectiveDef('greaterThan', function (a, b) {
|
||||
return a > b;
|
||||
}))
|
||||
.directive('lessThan', makeDirectiveDef('lessThan', function (a, b) {
|
||||
return a < b;
|
||||
}));
|
||||
});
|
|
@ -14,6 +14,11 @@ define(function (require) {
|
|||
ngModel.$formatters.unshift(validateCidrMask);
|
||||
|
||||
function validateCidrMask(mask) {
|
||||
if (mask == null || mask === '') {
|
||||
ngModel.$setValidity('cidrMaskInput', true);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
mask = new CidrMask(mask);
|
||||
ngModel.$setValidity('cidrMaskInput', true);
|
||||
|
|
|
@ -13,6 +13,11 @@ define(function (require) {
|
|||
},
|
||||
link: function ($scope, elem, attr, ngModel) {
|
||||
function validateIp(ipAddress) {
|
||||
if (ipAddress == null || ipAddress === '') {
|
||||
ngModel.$setValidity('ipInput', true);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
ipAddress = new Ipv4Address(ipAddress);
|
||||
ngModel.$setValidity('ipInput', true);
|
||||
|
|
|
@ -94,7 +94,7 @@ define(function (require) {
|
|||
|
||||
timefilter.enabled = true;
|
||||
$scope.timefilter = timefilter;
|
||||
$scope.$listen(timefilter, 'update', $scope.refresh);
|
||||
$scope.$listen(timefilter, 'fetch', $scope.refresh);
|
||||
|
||||
courier.setRootSearchSource(dash.searchSource);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ define(function (require) {
|
|||
require('components/courier/courier');
|
||||
require('components/index_patterns/index_patterns');
|
||||
require('components/state_management/app_state');
|
||||
require('services/timefilter');
|
||||
require('components/timefilter/timefilter');
|
||||
require('components/highlight/highlight_tags');
|
||||
|
||||
var app = require('modules').get('apps/discover', [
|
||||
|
@ -148,7 +148,7 @@ define(function (require) {
|
|||
|
||||
$scope.updateDataSource()
|
||||
.then(function () {
|
||||
$scope.$listen(timefilter, 'update', function () {
|
||||
$scope.$listen(timefilter, 'fetch', function () {
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
from="timefilter.time.from"
|
||||
to="timefilter.time.to"
|
||||
mode="timefilter.time.mode"
|
||||
active-tab="timefilter.timepickerActiveTab"
|
||||
interval="timefilter.refreshInterval">
|
||||
</kbn-timepicker>
|
||||
</kbn-timepicker>
|
||||
|
|
|
@ -12,14 +12,21 @@ define(function (require) {
|
|||
});
|
||||
|
||||
var timepickerHtml = require('text!plugins/kibana/_timepicker.html');
|
||||
$scope.toggleTimepicker = function () {
|
||||
$scope.toggleTimepicker = function (tab) {
|
||||
tab = tab || timefilter.timepickerActiveTab || 'filter';
|
||||
|
||||
// Close if already open
|
||||
if ($scope.globalConfigTemplate === timepickerHtml) {
|
||||
if ($scope.globalConfigTemplate === timepickerHtml && timefilter.timepickerActiveTab === tab) {
|
||||
delete $scope.globalConfigTemplate;
|
||||
delete timefilter.timepickerActiveTab;
|
||||
} else {
|
||||
timefilter.timepickerActiveTab = tab;
|
||||
$scope.globalConfigTemplate = timepickerHtml;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleRefresh = function () {
|
||||
timefilter.refreshInterval.pause = !timefilter.refreshInterval.pause;
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,23 +22,36 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right navbar-timepicker">
|
||||
<li ng-show="httpActive.length" class="navbar-text hidden-xs">
|
||||
<div class="spinner"></div>
|
||||
<ul ng-if="setupComplete" ng-show="timefilter.enabled" class="nav navbar-nav navbar-right navbar-timepicker">
|
||||
<li>
|
||||
<a ng-click="toggleRefresh()"
|
||||
ng-show="timefilter.refreshInterval.value > 0">
|
||||
<i class="fa" ng-class="timefilter.refreshInterval.pause ? 'fa-play' : 'fa-pause'"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="setupComplete" ng-show="timefilter.enabled" class="navbar-timepicker-container">
|
||||
<a ng-click="toggleTimepicker()" aria-haspopup="true" aria-expanded="false">
|
||||
<span ng-show="timefilter.refreshInterval.value > 0" class="navbar-timepicker-auto-refresh-desc">
|
||||
{{timefilter.refreshInterval.display}}
|
||||
<i aria-hidden="true" class="fa fa-rotate-right"></i>
|
||||
</span>
|
||||
<span class="navbar-timepicker-timefilter-desc">
|
||||
<pretty-duration from="timefilter.time.from" to="timefilter.time.to"></pretty-duration>
|
||||
<i aria-hidden="true" class="fa fa-clock-o"></i>
|
||||
</span>
|
||||
<li ng-class="{active: timefilter.timepickerActiveTab === 'interval'}"
|
||||
ng-show="timefilter.refreshInterval.value > 0 || timefilter.timepickerActiveTab"
|
||||
class="to-body">
|
||||
<a ng-click="toggleTimepicker('interval')"
|
||||
class="navbar-timepicker-auto-refresh-desc">
|
||||
<span ng-show="timefilter.refreshInterval.value === 0"><i class="fa fa-repeat"></i> Auto-refresh</span>
|
||||
<span ng-show="timefilter.refreshInterval.value > 0">{{timefilter.refreshInterval.display}}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="to-body" ng-class="{active: timefilter.timepickerActiveTab === 'filter'}">
|
||||
<a ng-click="toggleTimepicker('filter')" aria-haspopup="true" aria-expanded="false" class="navbar-timepicker-time-desc">
|
||||
<i aria-hidden="true" class="fa fa-clock-o"></i>
|
||||
<pretty-duration from="timefilter.time.from" to="timefilter.time.to"></pretty-duration>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right navbar-timepicker" >
|
||||
<li ng-show="httpActive.length" class="navbar-text hidden-xs">
|
||||
<div class="spinner"></div>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- /Full navbar -->
|
||||
</div>
|
||||
|
@ -47,7 +60,8 @@
|
|||
<config
|
||||
ng-show="timefilter.enabled"
|
||||
config-template="globalConfigTemplate"
|
||||
config-object="timefilter">
|
||||
config-object="timefilter"
|
||||
config-close="toggleTimepicker">
|
||||
</config>
|
||||
|
||||
<div class="application" ng-view></div>
|
||||
|
|
|
@ -1,14 +1,49 @@
|
|||
<div>
|
||||
<div class="vis-option-item">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.defaultYExtents">
|
||||
Scale Y-Axis to Data Bounds
|
||||
</label>
|
||||
</div>
|
||||
<div class="vis-option-item" ng-show="vis.hasSchemaAgg('segment', 'date_histogram')">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.addTimeMarker">
|
||||
<input type="checkbox" ng-model="vis.params.addTimeMarker" ng-checked="vis.params.addTimeMarker">
|
||||
Current time marker
|
||||
</label>
|
||||
</div>
|
||||
<div class="vis-option-item">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.setYExtents">
|
||||
Set Y-Axis Extents
|
||||
</label>
|
||||
<div ng-if="vis.params.setYExtents">
|
||||
<label>
|
||||
y-max
|
||||
<input name="yMax"
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="0.1"
|
||||
greater-than="{{vis.params.yAxis.min}}"
|
||||
ng-model="vis.params.yAxis.max"
|
||||
ng-required="vis.params.setYExtents">
|
||||
</label>
|
||||
<div ng-show="vis.params.yAxis.min > vis.params.yAxis.max">
|
||||
<span class="text-danger">Min must not exceed max</span>
|
||||
</div>
|
||||
<label>
|
||||
y-min
|
||||
<input name="yMin"
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="0.1"
|
||||
less-than="{{vis.params.yAxis.max}}"
|
||||
greater-than="{{vis.params.scale === 'log' ? 0 : ''}}"
|
||||
ng-model="vis.params.yAxis.min"
|
||||
ng-required="vis.params.setYExtents">
|
||||
</label>
|
||||
</div>
|
||||
<div ng-show="vis.params.setYExtents && vis.params.scale === 'log' && vis.params.yAxis.min <= 0">
|
||||
<span class="text-danger">Min must exceed 0 when a log scale is selected</span>
|
||||
</div>
|
||||
<div class="vis-option-item">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.defaultYExtents" ng-disabled="vis.params.setYExtents">
|
||||
Scale Y-Axis to Data Bounds
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var module = require('modules').get('kibana');
|
||||
require('directives/inequality');
|
||||
|
||||
module.directive('pointSeriesOptions', function ($parse, $compile) {
|
||||
return {
|
||||
|
|
|
@ -8,6 +8,7 @@ define(function (require) {
|
|||
var VislibRenderbot = Private(require('plugins/vis_types/vislib/_vislib_renderbot'));
|
||||
|
||||
require('plugins/vis_types/controls/vislib_basic_options');
|
||||
require('plugins/vis_types/controls/point_series_options');
|
||||
require('plugins/vis_types/controls/line_interpolation_option');
|
||||
require('plugins/vis_types/controls/point_series_options');
|
||||
|
||||
|
|
|
@ -18,11 +18,13 @@ define(function (require) {
|
|||
addLegend: true,
|
||||
smoothLines: false,
|
||||
scale: 'linear',
|
||||
mode: 'stacked',
|
||||
interpolate: 'linear',
|
||||
defaultYExtents: false,
|
||||
mode: 'stacked',
|
||||
times: [],
|
||||
addTimeMarker: false
|
||||
addTimeMarker: false,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {}
|
||||
},
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
modes: ['stacked', 'overlap', 'percentage', 'wiggle', 'silhouette'],
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
</label>
|
||||
</div>
|
||||
<line-interpolation-option></line-interpolation-option>
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
<point-series-options></point-series-options>
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
</label>
|
||||
<select class="form-control" ng-model="vis.params.mode" ng-options="mode for mode in vis.type.params.modes"></select>
|
||||
</div>
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
<point-series-options></point-series-options>
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
|
|
|
@ -19,5 +19,5 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
<point-series-options></point-series-options>
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
|
|
|
@ -1,19 +1,114 @@
|
|||
<!-- vis type specific options -->
|
||||
<div class="form-group">
|
||||
<label>Map type</label>
|
||||
<select
|
||||
name="agg"
|
||||
<select name="agg"
|
||||
class="form-control"
|
||||
ng-model="vis.params.mapType"
|
||||
ng-init="vis.params.mapType || vis.type.params.mapTypes[0]"
|
||||
ng-options="mapType as mapType for mapType in vis.type.params.mapTypes">
|
||||
ng-options="mapType as mapType for mapType in vis.type.params.mapTypes"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="vis-option-item">
|
||||
</br>
|
||||
<div ng-if="vis.params.mapType === 'Heatmap'" class="form-group">
|
||||
<div>
|
||||
<label>
|
||||
Radius
|
||||
<kbn-info placement="right" info="Size of heatmap dots. Default: 25"></kbn-info>
|
||||
</label>
|
||||
<div class="vis-editor-agg-form-row">
|
||||
<input
|
||||
name="heatRadius"
|
||||
ng-model="vis.params.heatRadius"
|
||||
required
|
||||
class="form-control"
|
||||
type="range"
|
||||
min="5"
|
||||
max="50"
|
||||
step="1"
|
||||
>
|
||||
<div class="form-group vis-editor-agg-form-value">
|
||||
{{vis.params.heatRadius}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Blur
|
||||
<kbn-info placement="right" info="Amount of blur applied to dots. Default: 15"></kbn-info>
|
||||
</label>
|
||||
<div class="vis-editor-agg-form-row">
|
||||
<input
|
||||
name="heatBlur"
|
||||
ng-model="vis.params.heatBlur"
|
||||
required
|
||||
class="form-control"
|
||||
type="range"
|
||||
min="1"
|
||||
max="25"
|
||||
step="1"
|
||||
>
|
||||
<div class="form-group vis-editor-agg-form-value">
|
||||
{{vis.params.heatBlur}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Maximum zoom
|
||||
<kbn-info placement="right" info="Map zoom at which all dots are displayed at full intensity. Default: 16"></kbn-info>
|
||||
</label>
|
||||
<div class="vis-editor-agg-form-row">
|
||||
<input
|
||||
name="heatMaxZoom"
|
||||
ng-model="vis.params.heatMaxZoom"
|
||||
required
|
||||
class="form-control"
|
||||
type="range"
|
||||
min="1"
|
||||
max="18"
|
||||
step="1"
|
||||
>
|
||||
<div class="vis-editor-agg-form-value">
|
||||
{{vis.params.heatMaxZoom}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Minimum opacity
|
||||
<kbn-info placement="right" info="Minimum opacity of dots. Default: 0.1"></kbn-info>
|
||||
</label>
|
||||
<div class="vis-editor-agg-form-row">
|
||||
<input
|
||||
name="heatMinOpacity"
|
||||
ng-model="vis.params.heatMinOpacity"
|
||||
required
|
||||
class="form-control"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.01"
|
||||
>
|
||||
<div class="vis-editor-agg-form-value">
|
||||
{{vis.params.heatMinOpacity}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.addTooltip">
|
||||
Show Tooltip
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vis-option-item form-group">
|
||||
<label>
|
||||
<input type="checkbox" value="{{isDesaturated}}" ng-model="vis.params.isDesaturated" name="isDesaturated" ng-checked="vis.params.isDesaturated">
|
||||
<input type="checkbox"
|
||||
name="isDesaturated"
|
||||
ng-model="vis.params.isDesaturated">
|
||||
Desaturate map tiles
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,9 +16,11 @@ define(function (require) {
|
|||
addLegend: true,
|
||||
scale: 'linear',
|
||||
mode: 'stacked',
|
||||
defaultYExtents: false,
|
||||
times: [],
|
||||
addTimeMarker: false
|
||||
addTimeMarker: false,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {}
|
||||
},
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
modes: ['stacked', 'percentage', 'grouped'],
|
||||
|
|
|
@ -17,12 +17,14 @@ define(function (require) {
|
|||
showCircles: true,
|
||||
smoothLines: false,
|
||||
interpolate: 'linear',
|
||||
scale: 'linear',
|
||||
drawLinesBetweenPoints: true,
|
||||
radiusRatio: 9,
|
||||
scale: 'linear',
|
||||
defaultYExtents: false,
|
||||
times: [],
|
||||
addTimeMarker: false
|
||||
addTimeMarker: false,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {}
|
||||
},
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
editor: require('text!plugins/vis_types/vislib/editors/line.html')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
define(function (require) {
|
||||
return function TileMapVisType(Private, getAppState) {
|
||||
return function TileMapVisType(Private, getAppState, courier, config) {
|
||||
var VislibVisType = Private(require('plugins/vis_types/vislib/_vislib_vis_type'));
|
||||
var Schemas = Private(require('plugins/vis_types/_schemas'));
|
||||
var geoJsonConverter = Private(require('components/agg_response/geo_json/geo_json'));
|
||||
|
@ -14,9 +14,15 @@ define(function (require) {
|
|||
params: {
|
||||
defaults: {
|
||||
mapType: 'Scaled Circle Markers',
|
||||
isDesaturated: true
|
||||
isDesaturated: true,
|
||||
heatMaxZoom: 16,
|
||||
heatMinOpacity: 0.1,
|
||||
heatRadius: 25,
|
||||
heatBlur: 15,
|
||||
heatNormalizeData: true,
|
||||
addTooltip: true
|
||||
},
|
||||
mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid'],
|
||||
mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid', 'Heatmap'],
|
||||
editor: require('text!plugins/vis_types/vislib/editors/tile_map.html')
|
||||
},
|
||||
listeners: {
|
||||
|
@ -31,6 +37,38 @@ define(function (require) {
|
|||
filter.geo_bounding_box[field] = event.bounds;
|
||||
|
||||
pushFilter(filter, false, indexPatternName);
|
||||
},
|
||||
mapZoomEnd: function (event) {
|
||||
var agg = _.deepGet(event, 'data.properties.agg.geo');
|
||||
if (!agg || !agg.params.autoPrecision) return;
|
||||
|
||||
// zoomPrecision maps event.zoom to a geohash precision value
|
||||
// event.limit is the configurable max geohash precision
|
||||
// default max precision is 7, configurable up to 12
|
||||
var zoomPrecision = {
|
||||
1: 2,
|
||||
2: 2,
|
||||
3: 2,
|
||||
4: 3,
|
||||
5: 3,
|
||||
6: 4,
|
||||
7: 4,
|
||||
8: 5,
|
||||
9: 5,
|
||||
10: 6,
|
||||
11: 6,
|
||||
12: 7,
|
||||
13: 7,
|
||||
14: 8,
|
||||
15: 9,
|
||||
16: 10,
|
||||
17: 11,
|
||||
18: 12
|
||||
};
|
||||
|
||||
agg.params.precision = Math.min(zoomPrecision[event.zoom], config.get('visualization:tileMap:maxPrecision'));
|
||||
|
||||
courier.fetch();
|
||||
}
|
||||
},
|
||||
responseConverter: geoJsonConverter,
|
||||
|
|
|
@ -19,6 +19,7 @@ define(function (require) {
|
|||
template: require('text!plugins/visualize/editor/agg.html'),
|
||||
require: 'form',
|
||||
link: function ($scope, $el, attrs, kbnForm) {
|
||||
$scope.$bind('outputAgg', 'outputVis.aggs.byId[agg.id]', $scope);
|
||||
$scope.editorOpen = !!$scope.agg.brandNew;
|
||||
if (!$scope.editorOpen) {
|
||||
$scope.$evalAsync(kbnForm.$setTouched);
|
||||
|
|
|
@ -194,7 +194,7 @@ define(function (require) {
|
|||
// Without this manual emission, we'd miss filters and queries that were on the $state initially
|
||||
$state.emit('fetch_with_changes');
|
||||
|
||||
$scope.$listen(timefilter, 'update', _.bindKey($scope, 'fetch'));
|
||||
$scope.$listen(timefilter, 'fetch', _.bindKey($scope, 'fetch'));
|
||||
|
||||
$scope.$on('ready:vis', function () {
|
||||
$scope.$emit('application.load');
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<div class="sidebar-container">
|
||||
<form class="sidebar-list"
|
||||
ng-submit="visualizeEditor.$invalid ? dontApply() : stageEditableVis()"
|
||||
name="visualizeEditor">
|
||||
name="visualizeEditor"
|
||||
novalidate><!-- see http://goo.gl/9kgz5w -->
|
||||
|
||||
<div css-truncate title="{{indexPattern.id}}" ng-if="vis.type.requiresSearch" class="index-pattern">
|
||||
{{ indexPattern.id }}
|
||||
|
|
|
@ -14,6 +14,7 @@ define(function (require) {
|
|||
controllerAs: 'sidebar',
|
||||
controller: function ($scope) {
|
||||
$scope.$bind('vis', 'editableVis');
|
||||
$scope.$bind('outputVis', 'vis');
|
||||
this.section = _.get($scope, 'vis.type.requiresSearch') ? 'data' : 'options';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
view options
|
||||
</div>
|
||||
<div class="visualization-options"></div>
|
||||
</li>
|
||||
</li>
|
|
@ -25,6 +25,7 @@ require.config({
|
|||
faker: 'bower_components/Faker/faker',
|
||||
file_saver: 'bower_components/FileSaver/FileSaver',
|
||||
gridster: 'bower_components/gridster/dist/jquery.gridster',
|
||||
'leaflet-heat': 'bower_components/Leaflet.heat/dist/leaflet-heat',
|
||||
inflection: 'bower_components/inflection/lib/inflection',
|
||||
jquery: 'bower_components/jquery/dist/jquery',
|
||||
leaflet: 'bower_components/leaflet/dist/leaflet',
|
||||
|
@ -51,6 +52,9 @@ require.config({
|
|||
'ace-json': ['ace'],
|
||||
'angular-ui-ace': ['angular', 'ace', 'ace-json'],
|
||||
'ng-clip': ['angular', 'zeroclipboard'],
|
||||
'leaflet-heat': {
|
||||
deps: ['leaflet']
|
||||
},
|
||||
inflection: {
|
||||
exports: 'inflection'
|
||||
},
|
||||
|
|
|
@ -12,4 +12,27 @@
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-active-arrow (@color: @body-bg) {
|
||||
|
||||
@media (max-width: @screen-sm-min) {
|
||||
&:before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content:"";
|
||||
display:inline-block;
|
||||
position:absolute;
|
||||
border:@navbar-arrow-size solid @color;
|
||||
border-color:transparent transparent @color transparent;
|
||||
top:(@navbar-height - @navbar-arrow-size*2);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
|
@ -258,12 +258,13 @@ notifications {
|
|||
}
|
||||
|
||||
.navbar-timepicker {
|
||||
&-auto-refresh-desc {
|
||||
margin-right: 20px;
|
||||
> li > a {
|
||||
padding-left: 7px !important;
|
||||
padding-right: 7px !important;
|
||||
}
|
||||
|
||||
&-timefilter-desc {
|
||||
|
||||
&-time-desc > .fa-clock-o {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.fa {
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
//@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
|
||||
|
||||
@import (reference) "../_mixins.less";
|
||||
|
||||
|
||||
// Navbar =====================================================================
|
||||
|
||||
.navbar {
|
||||
|
@ -32,26 +35,7 @@
|
|||
}
|
||||
|
||||
.navbar-nav > .active > a {
|
||||
|
||||
@media (max-width: @screen-sm-min) {
|
||||
&:before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content:"";
|
||||
display:inline-block;
|
||||
position:absolute;
|
||||
border:@navbar-arrow-size solid @body-bg;
|
||||
border-color:transparent transparent @body-bg transparent;
|
||||
top:(@navbar-height - @navbar-arrow-size*2);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.nav-active-arrow(@body-bg);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -73,26 +57,14 @@
|
|||
color: lighten(@navbar-inverse-bg, 10%);
|
||||
}
|
||||
|
||||
.navbar-nav > .active > a {
|
||||
|
||||
@media (max-width: @screen-sm-min) {
|
||||
&:before {
|
||||
display: none !important;
|
||||
}
|
||||
.navbar-nav {
|
||||
> .active > a {
|
||||
.nav-active-arrow(@navbar-default-bg);
|
||||
}
|
||||
|
||||
&:before {
|
||||
content:"";
|
||||
display:inline-block;
|
||||
position:absolute;
|
||||
border:@navbar-arrow-size solid @navbar-default-bg;
|
||||
border-color:transparent transparent @navbar-default-bg transparent;
|
||||
top:(@navbar-height - @navbar-arrow-size*2);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 0;
|
||||
height: 0;
|
||||
> .active.to-body > a {
|
||||
.nav-active-arrow(@body-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ define(function (require) {
|
|||
valueOf(rangeA.to) !== valueOf(rangeB.to)
|
||||
|| valueOf(rangeA.from) !== valueOf(rangeB.from)
|
||||
|| valueOf(rangeA.value) !== valueOf(rangeB.value)
|
||||
|| valueOf(rangeA.pause) !== valueOf(rangeB.pause)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
|
75
test/unit/specs/components/timefilter/diff_interval.js
Normal file
75
test/unit/specs/components/timefilter/diff_interval.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
define(function (require) {
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
describe('Timefilter service', function () {
|
||||
describe('Refresh interval diff watcher', function () {
|
||||
|
||||
var fn, update, fetch, timefilter;
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (Private) {
|
||||
update = sinon.spy();
|
||||
fetch = sinon.spy();
|
||||
timefilter = {
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 0
|
||||
},
|
||||
emit: function (eventType) {
|
||||
if (eventType === 'update') update();
|
||||
if (eventType === 'fetch') fetch();
|
||||
}
|
||||
};
|
||||
|
||||
fn = Private(require('components/timefilter/lib/diff_interval'))(timefilter);
|
||||
}));
|
||||
|
||||
it('not emit anything if nothing has changed', function () {
|
||||
timefilter.refreshInterval = {pause: false, value: 0};
|
||||
fn();
|
||||
expect(update.called).to.be(false);
|
||||
expect(fetch.called).to.be(false);
|
||||
});
|
||||
|
||||
it('emit only an update when paused', function () {
|
||||
timefilter.refreshInterval = {pause: true, value: 5000};
|
||||
fn();
|
||||
expect(update.called).to.be(true);
|
||||
expect(fetch.called).to.be(false);
|
||||
});
|
||||
|
||||
it('emit update, not fetch, when switching to value: 0', function () {
|
||||
timefilter.refreshInterval = {pause: false, value: 5000};
|
||||
fn();
|
||||
expect(update.calledOnce).to.be(true);
|
||||
expect(fetch.calledOnce).to.be(true);
|
||||
timefilter.refreshInterval = {pause: false, value: 0};
|
||||
fn();
|
||||
expect(update.calledTwice).to.be(true);
|
||||
expect(fetch.calledTwice).to.be(false);
|
||||
});
|
||||
|
||||
it('should emit update, not fetch, when moving from unpaused to paused', function () {
|
||||
timefilter.refreshInterval = {pause: false, value: 5000};
|
||||
fn();
|
||||
expect(update.calledOnce).to.be(true);
|
||||
expect(fetch.calledOnce).to.be(true);
|
||||
timefilter.refreshInterval = {pause: true, value: 5000};
|
||||
fn();
|
||||
expect(update.calledTwice).to.be(true);
|
||||
expect(fetch.calledTwice).to.be(false);
|
||||
});
|
||||
|
||||
it('should emit update and fetch when unpaused', function () {
|
||||
timefilter.refreshInterval = {pause: true, value: 5000};
|
||||
fn();
|
||||
expect(update.calledOnce).to.be(true);
|
||||
expect(fetch.calledOnce).to.be(false);
|
||||
timefilter.refreshInterval = {pause: false, value: 5000};
|
||||
fn();
|
||||
expect(update.calledTwice).to.be(true);
|
||||
expect(fetch.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
42
test/unit/specs/components/timefilter/diff_time.js
Normal file
42
test/unit/specs/components/timefilter/diff_time.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
define(function (require) {
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
describe('Timefilter service', function () {
|
||||
describe('time diff watcher', function () {
|
||||
|
||||
var fn, update, fetch, timefilter;
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (Private) {
|
||||
update = sinon.spy();
|
||||
fetch = sinon.spy();
|
||||
timefilter = {
|
||||
time: {
|
||||
from: 0,
|
||||
to: 1
|
||||
},
|
||||
emit: function (eventType) {
|
||||
if (eventType === 'update') update();
|
||||
if (eventType === 'fetch') fetch();
|
||||
}
|
||||
};
|
||||
|
||||
fn = Private(require('components/timefilter/lib/diff_time'))(timefilter);
|
||||
}));
|
||||
|
||||
it('not emit anything if the time has not changed', function () {
|
||||
timefilter.time = {from: 0, to: 1};
|
||||
fn();
|
||||
expect(update.called).to.be(false);
|
||||
expect(fetch.called).to.be(false);
|
||||
});
|
||||
|
||||
it('emit update and fetch if the time has changed', function () {
|
||||
timefilter.time = {from: 5, to: 10};
|
||||
fn();
|
||||
expect(update.called).to.be(true);
|
||||
expect(fetch.called).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
require('directives/greater_than');
|
||||
require('directives/inequality');
|
||||
|
||||
describe('greater_than model validator directive', function () {
|
||||
var $compile, $rootScope;
|
||||
|
@ -27,16 +27,16 @@ define(function (require) {
|
|||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should be invalid for 0', function () {
|
||||
it('should be valid for 0', function () {
|
||||
$rootScope.value = '0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should be invalid for negatives', function () {
|
||||
it('should be valid for negatives', function () {
|
||||
$rootScope.value = '-10';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
});
|
||||
|
|
@ -52,6 +52,7 @@ define(function (require) {
|
|||
' from="timefilter.time.from"' +
|
||||
' to="timefilter.time.to"' +
|
||||
' mode="timefilter.time.mode"' +
|
||||
' active-tab="timefilter.timepickerActiveTab"' +
|
||||
' interval="timefilter.refreshInterval">' +
|
||||
'</kbn-timepicker>'
|
||||
);
|
||||
|
@ -111,6 +112,35 @@ define(function (require) {
|
|||
done();
|
||||
});
|
||||
|
||||
it('should disable the looper when paused', function (done) {
|
||||
$scope.setRefreshInterval({ value : 1000, pause: true});
|
||||
$elem.scope().$digest();
|
||||
expect($courier.searchLooper.loopInterval()).to.be(0);
|
||||
expect($scope.interval.value).to.be(1000);
|
||||
done();
|
||||
});
|
||||
|
||||
it('but keep interval.value set', function (done) {
|
||||
$scope.setRefreshInterval({ value : 1000, pause: true});
|
||||
$elem.scope().$digest();
|
||||
expect($scope.interval.value).to.be(1000);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should unpause when setRefreshInterval is called without pause:true', function (done) {
|
||||
$scope.setRefreshInterval({ value : 1000, pause: true});
|
||||
expect($scope.interval.pause).to.be(true);
|
||||
|
||||
$scope.setRefreshInterval({ value : 1000, pause: false});
|
||||
expect($scope.interval.pause).to.be(false);
|
||||
|
||||
$scope.setRefreshInterval({ value : 1000});
|
||||
expect($scope.interval.pause).to.be(false);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('should highlight the current active interval', function (done) {
|
||||
$scope.setRefreshInterval({ value: 300000 });
|
||||
$elem.scope().$digest();
|
||||
|
|
|
@ -13,6 +13,22 @@ define(function (require) {
|
|||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should allow empty input', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = undefined;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should allow valid CIDR masks', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
|
@ -36,10 +52,6 @@ define(function (require) {
|
|||
it('should disallow invalid CIDR masks', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = 'hello, world';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
|
|
@ -13,6 +13,22 @@ define(function (require) {
|
|||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should allow empty input', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = undefined;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should allow valid IP addresses', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
|
@ -36,10 +52,6 @@ define(function (require) {
|
|||
it('should disallow invalid IP addresses', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = 'hello, world';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
@ -56,7 +68,7 @@ define(function (require) {
|
|||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = Number.MAX_SAFE_INTEGER;
|
||||
$rootScope.value = Number.MAX_VALUE;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
define(function (require) {
|
||||
return function VisLibFixtures(Private) {
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
return function (visLibParams) {
|
||||
var Vis = Private(require('components/vislib/vis'));
|
||||
|
@ -12,12 +13,15 @@ define(function (require) {
|
|||
$el.width(1024);
|
||||
$el.height(300);
|
||||
|
||||
var config = visLibParams || {
|
||||
var config = _.defaults(visLibParams || {}, {
|
||||
shareYAxis: true,
|
||||
addTooltip: true,
|
||||
addLegend: true,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {},
|
||||
type: 'histogram'
|
||||
};
|
||||
});
|
||||
|
||||
return new Vis($el, config);
|
||||
};
|
||||
|
|
|
@ -77,7 +77,10 @@ define(function (require) {
|
|||
yMin: dataObj.getYMin(),
|
||||
yMax: dataObj.getYMax(),
|
||||
_attr: {
|
||||
margin: { top: 0, right: 0, bottom: 0, left: 0 }
|
||||
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
defaultYMin: true,
|
||||
setYExtents: false,
|
||||
yAxis: {}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
@ -126,6 +129,7 @@ define(function (require) {
|
|||
describe('getYScale Method', function () {
|
||||
var yScale;
|
||||
var graphData;
|
||||
var domain;
|
||||
var height = 50;
|
||||
|
||||
function checkDomain(min, max) {
|
||||
|
@ -151,6 +155,20 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should return log values', function () {
|
||||
var domain;
|
||||
var extents;
|
||||
|
||||
it('should return 1', function () {
|
||||
yAxis._attr.scale = 'log';
|
||||
extents = [0, 400];
|
||||
domain = yAxis._getExtents(extents);
|
||||
|
||||
// Log scales have a yMin value of 1
|
||||
expect(domain[0]).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('positive values', function () {
|
||||
beforeEach(function () {
|
||||
graphData = defaultGraphData;
|
||||
|
@ -178,7 +196,6 @@ define(function (require) {
|
|||
yScale = yAxis.getYScale(height);
|
||||
});
|
||||
|
||||
|
||||
it('should have domain between min value and 0', function () {
|
||||
var min = _.min(_.flatten(graphData));
|
||||
var max = 0;
|
||||
|
@ -186,7 +203,6 @@ define(function (require) {
|
|||
expect(domain[0]).to.be.lessThan(0);
|
||||
checkRange();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('positive and negative values', function () {
|
||||
|
@ -199,7 +215,6 @@ define(function (require) {
|
|||
yScale = yAxis.getYScale(height);
|
||||
});
|
||||
|
||||
|
||||
it('should have domain between min and max values', function () {
|
||||
var min = _.min(_.flatten(graphData));
|
||||
var max = _.max(_.flatten(graphData));
|
||||
|
@ -210,20 +225,60 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should not return a nice scale when defaultYExtents is true', function () {
|
||||
describe('validate user defined values', function () {
|
||||
beforeEach(function () {
|
||||
createData(defaultGraphData);
|
||||
yAxis._attr.defaultYExtents = true;
|
||||
yAxis.getYAxis(height);
|
||||
yAxis.render();
|
||||
yAxis._attr.mode = 'stacked';
|
||||
yAxis._attr.setYExtents = false;
|
||||
yAxis._attr.yAxis = {};
|
||||
});
|
||||
|
||||
it('not return a nice scale', function () {
|
||||
var min = _.min(_.flatten(defaultGraphData));
|
||||
var max = _.max(_.flatten(defaultGraphData));
|
||||
var domain = yAxis.yAxis.scale().domain();
|
||||
expect(domain[0]).to.be(min);
|
||||
expect(domain[1]).to.be(max);
|
||||
it('should throw a NaN error', function () {
|
||||
var min = 'Not a number';
|
||||
var max = 12;
|
||||
|
||||
expect(function () {
|
||||
yAxis._validateUserExtents(min, max);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return a decimal value', function () {
|
||||
yAxis._attr.mode = 'percentage';
|
||||
yAxis._attr.setYExtents = true;
|
||||
domain = [];
|
||||
domain[0] = yAxis._attr.yAxis.min = 20;
|
||||
domain[1] = yAxis._attr.yAxis.max = 80;
|
||||
var newDomain = yAxis._validateUserExtents(domain);
|
||||
|
||||
expect(newDomain[0]).to.be(domain[0] / 100);
|
||||
expect(newDomain[1]).to.be(domain[1] / 100);
|
||||
});
|
||||
|
||||
it('should return the user defined value', function () {
|
||||
domain = [20, 50];
|
||||
var newDomain = yAxis._validateUserExtents(domain);
|
||||
|
||||
expect(newDomain[0]).to.be(domain[0]);
|
||||
expect(newDomain[1]).to.be(domain[1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should throw an error when', function () {
|
||||
it('min === max', function () {
|
||||
var min = 12;
|
||||
var max = 12;
|
||||
|
||||
expect(function () {
|
||||
yAxis._validateAxisExtents(min, max);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('min > max', function () {
|
||||
var min = 30;
|
||||
var max = 10;
|
||||
|
||||
expect(function () {
|
||||
yAxis._validateAxisExtents(min, max);
|
||||
}).to.throwError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -233,105 +288,39 @@ define(function (require) {
|
|||
|
||||
it('should return a function', function () {
|
||||
fnNames.forEach(function (fnName) {
|
||||
var isFunction = (typeof yAxis.getScaleType(fnName) === 'function');
|
||||
expect(isFunction).to.be(true);
|
||||
expect(yAxis._getScaleType(fnName)).to.be.a(Function);
|
||||
});
|
||||
|
||||
// if no value is provided to the function, scale should default to a linear scale
|
||||
expect(typeof yAxis.getScaleType()).to.be('function');
|
||||
expect(yAxis._getScaleType()).to.be.a(Function);
|
||||
});
|
||||
|
||||
it('should throw an error if function name is undefined', function () {
|
||||
expect(function () {
|
||||
yAxis.getScaleType('square');
|
||||
yAxis._getScaleType('square');
|
||||
}).to.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('returnLogDomain method', function () {
|
||||
describe('_logDomain method', function () {
|
||||
it('should throw an error', function () {
|
||||
expect(function () {
|
||||
yAxis.returnLogDomain(-10, -5);
|
||||
yAxis._logDomain(-10, -5);
|
||||
}).to.throwError();
|
||||
expect(function () {
|
||||
yAxis.returnLogDomain(-10, 5);
|
||||
yAxis._logDomain(-10, 5);
|
||||
}).to.throwError();
|
||||
expect(function () {
|
||||
yAxis.returnLogDomain(0, -5);
|
||||
yAxis._logDomain(0, -5);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return a yMin value of 1', function () {
|
||||
var yMin = yAxis.returnLogDomain(0, 200)[0];
|
||||
var yMin = yAxis._logDomain(0, 200)[0];
|
||||
expect(yMin).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDomain method', function () {
|
||||
beforeEach(function () {
|
||||
// Need to set this to false before each test since its
|
||||
// status changes in one of the tests below. Having this set to
|
||||
// true causes other tests to fail that need this attr to be set to false.
|
||||
yAxis._attr.defaultYExtents = false;
|
||||
});
|
||||
|
||||
it('should return a log domain', function () {
|
||||
var scale = 'log';
|
||||
var yMin = 0;
|
||||
var yMax = 400;
|
||||
var domain = yAxis.getDomain(scale, yMin, yMax);
|
||||
|
||||
// Log scales have a yMin value of 1
|
||||
expect(domain[0]).to.be(1);
|
||||
});
|
||||
|
||||
it('should return the default y axis extents (i.e. the min and max values)', function () {
|
||||
var scale = 'linear';
|
||||
var yMin = 25;
|
||||
var yMax = 150;
|
||||
var domain;
|
||||
|
||||
yAxis._attr.defaultYExtents = true;
|
||||
domain = yAxis.getDomain(scale, yMin, yMax);
|
||||
|
||||
expect(domain[0]).to.be(yMin);
|
||||
expect(domain[1]).to.be(yMax);
|
||||
});
|
||||
|
||||
it('should throw a no results error if yMin and yMax values are both 0', function () {
|
||||
expect(function () {
|
||||
yAxis.getDomain('linear', 0, 0);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return the correct min and max values', function () {
|
||||
var extents = [
|
||||
[-5, 20],
|
||||
[-30, -10],
|
||||
[5, 20]
|
||||
];
|
||||
|
||||
extents.forEach(function (extent) {
|
||||
var domain = yAxis.getDomain('linear', extent[0], extent[1]);
|
||||
|
||||
if (extent[0] < 0 && extent[1] > 0) {
|
||||
expect(domain[0]).to.be(extent[0]);
|
||||
expect(domain[1]).to.be(extent[1]);
|
||||
}
|
||||
|
||||
if (extent[0] < 0 && extent[1] < 0) {
|
||||
expect(domain[0]).to.be(extent[0]);
|
||||
expect(domain[1]).to.be(0);
|
||||
}
|
||||
|
||||
if (extent[0] > 0 && extent[1] > 0) {
|
||||
expect(domain[0]).to.be(0);
|
||||
expect(domain[1]).to.be(extent[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getYAxis method', function () {
|
||||
var mode, yMax, yScale;
|
||||
beforeEach(function () {
|
||||
|
|
|
@ -32,8 +32,7 @@ define(function (require) {
|
|||
var visLibParams = {
|
||||
type: 'area',
|
||||
addLegend: true,
|
||||
addTooltip: true,
|
||||
defaultYExtents: false
|
||||
addTooltip: true
|
||||
};
|
||||
|
||||
angular.module('AreaChartFactory', ['kibana']);
|
||||
|
@ -228,8 +227,8 @@ define(function (require) {
|
|||
vis.handler.charts.forEach(function (chart) {
|
||||
var yAxis = chart.handler.yAxis;
|
||||
|
||||
expect(yAxis.yMin).to.not.be(undefined);
|
||||
expect(yAxis.yMax).to.not.be(undefined);
|
||||
expect(yAxis.domain[0]).to.not.be(undefined);
|
||||
expect(yAxis.domain[1]).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -270,8 +269,8 @@ define(function (require) {
|
|||
var yAxis = chart.handler.yAxis;
|
||||
var yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()];
|
||||
|
||||
expect(yAxis.yMin).to.equal(yVals[0]);
|
||||
expect(yAxis.yMax).to.equal(yVals[1]);
|
||||
expect(yAxis.domain[0]).to.equal(yVals[0]);
|
||||
expect(yAxis.domain[1]).to.equal(yVals[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -166,8 +166,8 @@ define(function (require) {
|
|||
vis.handler.charts.forEach(function (chart) {
|
||||
var yAxis = chart.handler.yAxis;
|
||||
|
||||
expect(yAxis.yMin).to.not.be(undefined);
|
||||
expect(yAxis.yMax).to.not.be(undefined);
|
||||
expect(yAxis.domain[0]).to.not.be(undefined);
|
||||
expect(yAxis.domain[1]).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -208,8 +208,8 @@ define(function (require) {
|
|||
var yAxis = chart.handler.yAxis;
|
||||
var yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()];
|
||||
|
||||
expect(yAxis.yMin).to.equal(yVals[0]);
|
||||
expect(yAxis.yMax).to.equal(yVals[1]);
|
||||
expect(yAxis.domain[0]).to.equal(yVals[0]);
|
||||
expect(yAxis.domain[1]).to.equal(yVals[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -143,8 +143,8 @@ define(function (require) {
|
|||
vis.handler.charts.forEach(function (chart) {
|
||||
var yAxis = chart.handler.yAxis;
|
||||
|
||||
expect(yAxis.yMin).to.not.be(undefined);
|
||||
expect(yAxis.yMax).to.not.be(undefined);
|
||||
expect(yAxis.domain[0]).to.not.be(undefined);
|
||||
expect(yAxis.domain[1]).to.not.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -185,8 +185,8 @@ define(function (require) {
|
|||
var yAxis = chart.handler.yAxis;
|
||||
var yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()];
|
||||
|
||||
expect(yAxis.yMin).to.equal(yVals[0]);
|
||||
expect(yAxis.yMax).to.equal(yVals[1]);
|
||||
expect(yAxis.domain[0]).to.equal(yVals[0]);
|
||||
expect(yAxis.domain[1]).to.equal(yVals[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ define(function (require) {
|
|||
];
|
||||
var names = ['geojson', 'columns', 'rows'];
|
||||
// TODO: Test the specific behavior of each these
|
||||
var mapTypes = ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid'];
|
||||
var mapTypes = ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid', 'Heatmap'];
|
||||
|
||||
angular.module('TileMapFactory', ['kibana']);
|
||||
|
||||
|
@ -25,6 +25,7 @@ define(function (require) {
|
|||
mapType: type
|
||||
};
|
||||
|
||||
|
||||
module('TileMapFactory');
|
||||
inject(function (Private) {
|
||||
vis = Private(require('vislib_fixtures/_vis_fixture'))(visLibParams);
|
||||
|
@ -130,10 +131,26 @@ define(function (require) {
|
|||
describe('Methods', function () {
|
||||
var vis;
|
||||
var leafletContainer;
|
||||
var map;
|
||||
var mapData;
|
||||
var i;
|
||||
var feature;
|
||||
var point;
|
||||
var min;
|
||||
var max;
|
||||
var zoom;
|
||||
|
||||
beforeEach(function () {
|
||||
vis = bootstrapAndRender(dataArray[0], 'Scaled Circle Markers');
|
||||
leafletContainer = $(vis.el).find('.leaflet-container');
|
||||
map = vis.handler.charts[0].maps[0];
|
||||
mapData = vis.data.geoJson;
|
||||
i = _.random(0, mapData.features.length - 1);
|
||||
feature = mapData.features[i];
|
||||
point = feature.properties.latLng;
|
||||
min = mapData.properties.allmin;
|
||||
max = mapData.properties.allmax;
|
||||
zoom = _.random(1, 12);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
@ -167,20 +184,47 @@ define(function (require) {
|
|||
describe('geohashMinDistance method', function () {
|
||||
it('should return a number', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var feature = chart.chartData.geoJson.features[0];
|
||||
expect(_.isNumber(chart.geohashMinDistance(feature))).to.be(true);
|
||||
expect(_.isFinite(chart.geohashMinDistance(feature))).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('radiusScale method', function () {
|
||||
it('should return a number', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var count = Math.random() * 50;
|
||||
var max = 50;
|
||||
var precision = 1;
|
||||
var feature = chart.chartData.geoJson.features[0];
|
||||
expect(_.isNumber(chart.radiusScale(count, max, feature))).to.be(true);
|
||||
var countdata = [0, 10, 20, 30, 40, 50, 60];
|
||||
var max = 60;
|
||||
var zoom = _.random(1, 18);
|
||||
var constantZoomRadius = 0.5 * Math.pow(2, zoom);
|
||||
var precision = _.random(1, 12);
|
||||
var precisionScale = 200 / Math.pow(5, precision);
|
||||
var prev = -1;
|
||||
|
||||
it('test array should return a number equal to radius', function () {
|
||||
countdata.forEach(function (data, i) {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var count = data;
|
||||
var pct = count / max;
|
||||
var exp = 0.5;
|
||||
var radius = Math.pow(pct, exp) * constantZoomRadius * precisionScale;
|
||||
var test = chart.radiusScale(count, max, zoom, precision);
|
||||
|
||||
expect(test).to.be.a('number');
|
||||
expect(test).to.be(radius);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('test array should return a radius greater than previous', function () {
|
||||
countdata.forEach(function (data, i) {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var count = data;
|
||||
var pct = count / max;
|
||||
var exp = 0.5;
|
||||
var radius = Math.pow(pct, exp) * constantZoomRadius * precisionScale;
|
||||
var test = chart.radiusScale(count, max, zoom, precision);
|
||||
|
||||
expect(test).to.be.above(prev);
|
||||
prev = chart.radiusScale(count, max, zoom, precision);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -227,7 +271,127 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataToHeatArray method', function () {
|
||||
it('should return an array', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.dataToHeatArray(mapData, max)).to.be.an(Array);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array item for each feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.dataToHeatArray(mapData, max).length).to.be(mapData.features.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array item with lat, lng, metric for each feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var lat = feature.geometry.coordinates[1];
|
||||
var lng = feature.geometry.coordinates[0];
|
||||
var intensity = feature.properties.count;
|
||||
var array = chart.dataToHeatArray(mapData, max);
|
||||
expect(array[i][0]).to.be(lat);
|
||||
expect(array[i][1]).to.be(lng);
|
||||
expect(array[i][2]).to.be(intensity);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array item with lat, lng, normalized metric for each feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
chart._attr.heatNormalizeData = true;
|
||||
var lat = feature.geometry.coordinates[1];
|
||||
var lng = feature.geometry.coordinates[0];
|
||||
var intensity = parseInt(feature.properties.count / max * 100);
|
||||
var array = chart.dataToHeatArray(mapData, max);
|
||||
expect(array[i][0]).to.be(lat);
|
||||
expect(array[i][1]).to.be(lng);
|
||||
expect(array[i][2]).to.be(intensity);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('applyShadingStyle method', function () {
|
||||
it('should return an object', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.applyShadingStyle(feature, min, max)).to.be.an(Object);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('showTooltip method', function () {
|
||||
it('should create a .leaflet-popup-kibana div for the tooltip', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
chart.tooltipFormatter = function (str) {
|
||||
return '<div class="popup-stub"></div>';
|
||||
};
|
||||
var layerIds = _.keys(map._layers);
|
||||
var id = layerIds[_.random(1, layerIds.length - 1)]; // layer 0 is tileLayer
|
||||
map._layers[id].fire('mouseover');
|
||||
expect($('.popup-stub', vis.el).length).to.be(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tooltipProximity method', function () {
|
||||
it('should return true if feature is close enough to event latlng to display tooltip', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.tooltipProximity(point, zoom, feature, map)).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false if feature is not close enough to event latlng to display tooltip', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var point = L.latLng(90, -180);
|
||||
expect(chart.tooltipProximity(point, zoom, feature, map)).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('nearestFeature method', function () {
|
||||
it('should return an object', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.nearestFeature(point, mapData)).to.be.an(Object);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a geoJson feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.nearestFeature(point, mapData).type).to.be('Feature');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the geoJson feature with same latlng as point', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.nearestFeature(point, mapData)).to.be(feature);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addLatLng method', function () {
|
||||
it('should add object to properties of each feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(feature.properties.latLng).to.be.an(Object);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add latLng with lat to properties of each feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var lat = feature.geometry.coordinates[1];
|
||||
expect(feature.properties.latLng.lat).to.be(lat);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add latLng with lng to properties of each feature', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var lng = feature.geometry.coordinates[0];
|
||||
expect(feature.properties.latLng.lng).to.be(lng);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue