mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' into plugins
Conflicts: src/kibana/components/agg_types/param_types/optioned.js src/kibana/plugins/visualize/editor/agg.js test/unit/index.html
This commit is contained in:
commit
f80e25d63d
111 changed files with 2494 additions and 808 deletions
|
@ -23,6 +23,7 @@
|
|||
"angular": "~1.2.14",
|
||||
"angular-bindonce": "~0.3.1",
|
||||
"angular-bootstrap": "~0.10.0",
|
||||
"angular-chosen-localytics": "~1.0.6",
|
||||
"angular-elastic": "~2.3.3",
|
||||
"angular-mocks": "~1.2.14",
|
||||
"angular-route": "~1.2.14",
|
||||
|
@ -30,6 +31,7 @@
|
|||
"async": "~0.2.10",
|
||||
"bluebird": "~2.1.3",
|
||||
"bootstrap": "~3.1.1",
|
||||
"bootstrap-chosen": "61ed40742c",
|
||||
"d3": "~3.4.8",
|
||||
"elasticsearch": "*",
|
||||
"Faker": "~1.1.0",
|
||||
|
@ -45,7 +47,8 @@
|
|||
"moment-timezone": "~0.0.3",
|
||||
"require-css": "~0.1.2",
|
||||
"requirejs": "~2.1.10",
|
||||
"requirejs-text": "~2.0.10"
|
||||
"requirejs-text": "~2.0.10",
|
||||
"ng-clip": "~0.2.4"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
|
8
src/kibana/apps/visualize/partials/advanced_toggle.html
Normal file
8
src/kibana/apps/visualize/partials/advanced_toggle.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="advanced-toggle">
|
||||
<div ng-show="!advancedToggled">
|
||||
<a ng-click="advancedToggled = !advancedToggled">Advanced Parameters</a>
|
||||
</div>
|
||||
<div ng-show="advancedToggled">
|
||||
<a ng-click="advancedToggled = !advancedToggled">Hide Advanced</a>
|
||||
</div>
|
||||
</div>
|
|
@ -6,6 +6,7 @@ define(function (require) {
|
|||
var BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
var FieldAggParam = Private(require('components/agg_types/param_types/field'));
|
||||
var OptionedAggParam = Private(require('components/agg_types/param_types/optioned'));
|
||||
var RegexAggParam = Private(require('components/agg_types/param_types/regex'));
|
||||
|
||||
/**
|
||||
* Wraps a list of {{#crossLink "AggParam"}}{{/crossLink}} objects; owned by an {{#crossLink "AggType"}}{{/crossLink}}
|
||||
|
@ -36,9 +37,12 @@ define(function (require) {
|
|||
if (param.name === 'field') {
|
||||
return new FieldAggParam(param);
|
||||
}
|
||||
else if (param.options) {
|
||||
else if (param.type === 'optioned') {
|
||||
return new OptionedAggParam(param);
|
||||
}
|
||||
else if (param.type === 'regex') {
|
||||
return new RegexAggParam(param);
|
||||
}
|
||||
else {
|
||||
return new BaseAggParam(param);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ define(function (require) {
|
|||
|
||||
{
|
||||
name: 'interval',
|
||||
type: 'optioned',
|
||||
default: 'auto',
|
||||
options: Private(require('components/agg_types/buckets/_interval_options')),
|
||||
editor: require('text!components/agg_types/controls/interval.html'),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
define(function (require) {
|
||||
return function TermsAggDefinition(Private) {
|
||||
var _ = require('lodash');
|
||||
require('filters/label');
|
||||
var AggType = Private(require('components/agg_types/_agg_type'));
|
||||
var bucketCountBetween = Private(require('components/agg_types/buckets/_bucket_count_between'));
|
||||
|
||||
|
@ -22,6 +23,7 @@ define(function (require) {
|
|||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'optioned',
|
||||
options: [
|
||||
{ display: 'Top', val: 'desc' },
|
||||
{ display: 'Bottom', val: 'asc' }
|
||||
|
@ -54,6 +56,16 @@ define(function (require) {
|
|||
output.subAggs.push(metricAggConfig);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'exclude',
|
||||
type: 'regex',
|
||||
advanced: true
|
||||
},
|
||||
{
|
||||
name: 'include',
|
||||
type: 'regex',
|
||||
advanced: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<div class="form-group regex">
|
||||
<label>{{ aggParam.name | label }}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="params[aggParam.name].pattern"
|
||||
>
|
||||
<div class="flags">
|
||||
<div class="docs">
|
||||
<small><a href="http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html#field_detail"
|
||||
target="_blank">Regex Flags</a></small>
|
||||
</div>
|
||||
<select
|
||||
multiple
|
||||
chosen
|
||||
width="'100%'"
|
||||
data-placeholder="Flags"
|
||||
ng-model="params[aggParam.name].flags"
|
||||
ng-options="label for label in aggParam.flags">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
|
@ -41,7 +41,7 @@ define(function (require) {
|
|||
* @param {AggConfig} aggConfig - the entire configuration for this agg
|
||||
* @param {object} output - the result of calling write on all of the aggregations
|
||||
* parameters.
|
||||
* @param {object} output.param - the final object that will be included as the params
|
||||
* @param {object} output.params - the final object that will be included as the params
|
||||
* for the agg
|
||||
* @return {undefined}
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
|
||||
var IndexedArray = require('utils/indexed_array/index');
|
||||
var editorHtml = require('text!components/agg_types/controls/field.html');
|
||||
var Registry = require('utils/registry/registry');
|
||||
var BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
|
||||
_(OptionedAggParam).inherits(BaseAggParam);
|
||||
|
@ -43,7 +43,7 @@ define(function (require) {
|
|||
* @param {AggConfig} aggConfig - the entire configuration for this agg
|
||||
* @param {object} output - the result of calling write on all of the aggregations
|
||||
* parameters.
|
||||
* @param {object} output.param - the final object that will be included as the params
|
||||
* @param {object} output.params - the final object that will be included as the params
|
||||
* for the agg
|
||||
* @return {undefined}
|
||||
*/
|
||||
|
|
62
src/kibana/components/agg_types/param_types/regex.js
Normal file
62
src/kibana/components/agg_types/param_types/regex.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
define(function (require) {
|
||||
return function RegexAggParamFactory(Private) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
var editorHtml = require('text!components/agg_types/controls/regular_expression.html');
|
||||
|
||||
_(RegexAggParam).inherits(BaseAggParam);
|
||||
function RegexAggParam(config) {
|
||||
// Java RegExp flags
|
||||
var flags = [
|
||||
'CANON_EQ',
|
||||
'CASE_INSENSITIVE',
|
||||
'COMMENTS',
|
||||
'DOTALL',
|
||||
'LITERAL',
|
||||
'MULTILINE',
|
||||
'UNICODE_CASE',
|
||||
'UNICODE_CHARACTER_CLASS',
|
||||
'UNIX_LINES'
|
||||
];
|
||||
|
||||
_.defaults(config, { pattern: '', flags: flags });
|
||||
RegexAggParam.Super.call(this, config);
|
||||
}
|
||||
|
||||
RegexAggParam.prototype.editor = editorHtml;
|
||||
|
||||
/**
|
||||
* Write the aggregation parameter.
|
||||
*
|
||||
* @param {AggConfig} aggConfig - the entire configuration for this agg
|
||||
* @param {object} output - the result of calling write on all of the aggregations
|
||||
* parameters.
|
||||
* @param {object} output.params - the final object that will be included as the params
|
||||
* for the agg
|
||||
* @return {undefined}
|
||||
*/
|
||||
RegexAggParam.prototype.write = function (aggConfig, output) {
|
||||
var param = aggConfig.params[this.name];
|
||||
|
||||
// clear aggParam if pattern is not set
|
||||
if (!param || !param.pattern || !param.pattern.length) {
|
||||
delete output.params[this.name];
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = {
|
||||
pattern: param.pattern
|
||||
};
|
||||
|
||||
// include any selected flags
|
||||
if (_.isArray(param.flags) && param.flags.length) {
|
||||
obj.flags = param.flags.join('|');
|
||||
}
|
||||
|
||||
output.params[this.name] = obj;
|
||||
};
|
||||
|
||||
return RegexAggParam;
|
||||
};
|
||||
});
|
9
src/kibana/components/clipboard/clipboard.html
Normal file
9
src/kibana/components/clipboard/clipboard.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<span>
|
||||
<a tooltip="{{shownText}}"
|
||||
tooltip-placement="{{tipPlacement}}"
|
||||
tooltip-animation="false"
|
||||
tooltip-popup-delay="0"
|
||||
tooltip-append-to-body="false">
|
||||
<i class="fa" ng-class="icon" clip-copy="copyText"></i>
|
||||
</a>
|
||||
</span>
|
41
src/kibana/components/clipboard/clipboard.js
Normal file
41
src/kibana/components/clipboard/clipboard.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
define(function (require) {
|
||||
var $ = require('jquery');
|
||||
var html = require('text!components/clipboard/clipboard.html');
|
||||
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.directive('kbnClipboard', function ($compile, $timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: html,
|
||||
replace: true,
|
||||
scope: {
|
||||
copyText: '=copy'
|
||||
},
|
||||
transclude: true,
|
||||
link: function ($scope, $el, attr) {
|
||||
$scope.tipPlacement = attr.tipPlacement || 'top';
|
||||
$scope.tipText = attr.tipText || 'Copy to clipboard';
|
||||
$scope.tipConfirm = attr.tipConfirm = 'Copied!';
|
||||
$scope.icon = attr.icon || 'fa-clipboard';
|
||||
|
||||
$scope.shownText = $scope.tipText;
|
||||
|
||||
$el.on('click', function () {
|
||||
$scope.shownText = $scope.tipConfirm;
|
||||
// Reposition tooltip to account for text length change
|
||||
$('a', $el).mouseenter();
|
||||
});
|
||||
|
||||
$el.on('mouseleave', function () {
|
||||
$scope.shownText = $scope.tipText;
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$el.off('click');
|
||||
$el.off('mouseleave');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -1,15 +1,46 @@
|
|||
<div class="bar" ng-show="filters.length">
|
||||
<div class="filter" ng-class="{ negate: filter.negate, disabled: filter.disabled }" ng-repeat="filter in filters">
|
||||
<div class="filter-description">
|
||||
<!--<span><i class="fa" ng-class="{'fa-plus': !filter.negate, 'fa-minus': filter.negate}"></i></span>-->
|
||||
<span>{{ filter.key }}:</span>
|
||||
<span>"{{ filter.value }}"</span>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<a class="filter-toggle" ng-click="toggleFilter(filter)"><span ng-show="filter.disabled">enable</span><span ng-hide="filter.disabled">disable</span></a>
|
||||
<a class="filter-remove" ng-click="removeFilter(filter)">remove</a>
|
||||
<a class="filter-toggle" ng-click="toggleFilter(filter)">
|
||||
<span ng-show="filter.disabled">enable</span>
|
||||
<span ng-hide="filter.disabled">disable</span>
|
||||
</a>
|
||||
<a class="filter-remove" ng-click="removeFilter(filter)">
|
||||
remove
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-remove-all" ng-click="removeFilters()">
|
||||
<div class="filter-description"><a ng-click="removeAll()">remove all</a></div>
|
||||
<div class="filter-link">
|
||||
<div class="filter-description small">
|
||||
<a ng-click="showFilterActions = !showFilterActions">
|
||||
Actions
|
||||
<i class="fa"
|
||||
ng-class="{'fa-caret-down': showFilterActions, 'fa-caret-right': !showFilterActions}"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar bar-condensed" ng-show="filters.length && showFilterActions">
|
||||
<div class="filter-actions-all">
|
||||
<div class="filter-link">
|
||||
<div class="filter-description"><strong>All filters:</strong></div>
|
||||
</div>
|
||||
<div class="filter-link">
|
||||
<div class="filter-description"><a ng-click="toggleAll(false)">Enable</a></div>
|
||||
</div>
|
||||
<div class="filter-link">
|
||||
<div class="filter-description"><a ng-click="toggleAll(true)">Disable</a></div>
|
||||
</div>
|
||||
<div class="filter-link">
|
||||
<div class="filter-description"><a ng-click="toggleAll()">Toggle</a></div>
|
||||
</div>
|
||||
<div class="filter-link">
|
||||
<div class="filter-description"><a ng-click="removeAll()">Remove</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,10 +4,11 @@ define(function (require) {
|
|||
var module = require('modules').get('kibana');
|
||||
var template = require('text!components/filter_bar/filter_bar.html');
|
||||
|
||||
var mapFilter = require('./lib/mapFilter');
|
||||
var toggleFilter = require('./lib/toggleFilter');
|
||||
var removeFilter = require('./lib/removeFilter');
|
||||
var removeAll = require('./lib/removeAll');
|
||||
var mapFilter = require('components/filter_bar/lib/mapFilter');
|
||||
var toggleFilter = require('components/filter_bar/lib/toggleFilter');
|
||||
var toggleAll = require('components/filter_bar/lib/toggleAll');
|
||||
var removeFilter = require('components/filter_bar/lib/removeFilter');
|
||||
var removeAll = require('components/filter_bar/lib/removeAll');
|
||||
|
||||
module.directive('filterBar', function (courier) {
|
||||
return {
|
||||
|
@ -33,6 +34,7 @@ define(function (require) {
|
|||
$scope.toggleFilter = toggleFilter($scope);
|
||||
$scope.removeFilter = removeFilter($scope);
|
||||
$scope.removeAll = removeAll($scope);
|
||||
$scope.toggleAll = toggleAll($scope);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -7,7 +7,13 @@ filter-bar .bar {
|
|||
background: @gray-lighter;
|
||||
border-bottom: 1px solid #ccc;
|
||||
|
||||
.filter-remove-all {
|
||||
&-condensed {
|
||||
padding: 2px 6px 0px 6px !important;
|
||||
font-size: 0.9em;
|
||||
background: darken(@gray-lighter, 5%);
|
||||
}
|
||||
|
||||
.filter-link {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 4px solid transparent;
|
||||
|
@ -27,18 +33,14 @@ filter-bar .bar {
|
|||
min-width: 110px;
|
||||
|
||||
font-size: @font-size-small;
|
||||
background-color: @gray-dark;
|
||||
background-color: @action-add;
|
||||
color: #fff;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
max-width: 100%;
|
||||
|
||||
// Replace padding with border so absolute controls position correctly
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 4px;
|
||||
border-left-width: 8px;
|
||||
border-right-width: 8px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
|
||||
|
||||
|
@ -49,12 +51,14 @@ filter-bar .bar {
|
|||
|
||||
> .filter-description {
|
||||
opacity: 0.15;
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
> .filter-actions {
|
||||
position: absolute;
|
||||
padding: 4px 8px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
@ -70,7 +74,7 @@ filter-bar .bar {
|
|||
}
|
||||
|
||||
&.negate {
|
||||
background-color: #D18282;
|
||||
background-color: @action-remove;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -78,7 +82,8 @@ filter-bar .bar {
|
|||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
opacity: 0.6;
|
||||
background-image: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(255,255,255,.3) 10px, rgba(255,255,255,.3) 20px);
|
||||
}
|
||||
|
||||
&.disabled:hover {
|
||||
|
|
21
src/kibana/components/filter_bar/lib/toggleAll.js
Normal file
21
src/kibana/components/filter_bar/lib/toggleAll.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
return function ($scope) {
|
||||
|
||||
var remapFilters = require('components/filter_bar/lib/remapFilters');
|
||||
var toggleFilter = require('components/filter_bar/lib/toggleFilter')($scope);
|
||||
|
||||
/**
|
||||
* Disables all filters
|
||||
* @params {boolean} force disable/enable all filters
|
||||
* @returns {void}
|
||||
*/
|
||||
return function (force) {
|
||||
$scope.filters.forEach(function (filter) {
|
||||
toggleFilter(filter, force);
|
||||
});
|
||||
|
||||
$scope.state.filters = _.map($scope.filters, remapFilters);
|
||||
};
|
||||
};
|
||||
});
|
|
@ -5,11 +5,12 @@ define(function (require) {
|
|||
/**
|
||||
* Toggles the filter between enabled/disabled.
|
||||
* @param {object} filter The filter to toggle
|
||||
& @param {boolean} force disabled true/false
|
||||
* @returns {void}
|
||||
*/
|
||||
return function (filter) {
|
||||
return function (filter, force) {
|
||||
// Toggle the disabled flag
|
||||
var disabled = !filter.disabled;
|
||||
var disabled = _.isUndefined(force) ? !filter.disabled : force;
|
||||
filter.disabled = disabled;
|
||||
filter.filter.disabled = disabled;
|
||||
|
||||
|
|
|
@ -8,17 +8,22 @@ define(function (require) {
|
|||
* Accepts an array of strings or numbers that are used to create a
|
||||
* a lookup table that associates the values (key) with a hex color (value).
|
||||
* Returns a function that accepts a value (i.e. a string or number)
|
||||
* and returns a hex color associated with that value
|
||||
* and returns a hex color associated with that value.
|
||||
*/
|
||||
|
||||
return function (arrayOfStringsOrNumbers) {
|
||||
// Takes an array of strings or numbers
|
||||
if (!_.isArray(arrayOfStringsOrNumbers)) {
|
||||
throw new Error('ColorUtil expects an array of strings or numbers');
|
||||
throw new Error('ColorUtil expects an array');
|
||||
}
|
||||
|
||||
// Creates lookup table of values (keys) and hex colors (values).
|
||||
var colorObj = _.zipObject(arrayOfStringsOrNumbers, createColorPalette(arrayOfStringsOrNumbers.length));
|
||||
arrayOfStringsOrNumbers.forEach(function (val) {
|
||||
if (!_.isString(val) && !_.isNumber(val) && !_.isUndefined(val)) {
|
||||
throw new TypeError('ColorUtil expects an array of strings, numbers, or undefined values');
|
||||
}
|
||||
});
|
||||
|
||||
var arrayLength = arrayOfStringsOrNumbers.length;
|
||||
var colorObj = _.zipObject(arrayOfStringsOrNumbers, createColorPalette(arrayLength));
|
||||
|
||||
return function (value) {
|
||||
return colorObj[value];
|
||||
|
|
|
@ -4,15 +4,21 @@ define(function (require) {
|
|||
|
||||
var seedColors = Private(require('components/vislib/components/color/seed_colors'));
|
||||
|
||||
// Accepts a number that represents a length of an array
|
||||
return function (num) {
|
||||
var usedColors = [];
|
||||
/*
|
||||
* Generates an array of hex colors the length of the input number.
|
||||
* If the number is greater than the length of seed colors available,
|
||||
* new colors are generated up to the value of the input number.
|
||||
*/
|
||||
|
||||
// checks if more colors are needed
|
||||
return function (num) {
|
||||
if (!_.isNumber(num)) {
|
||||
throw new TypeError('ColorPaletteUtilService expects a number');
|
||||
}
|
||||
|
||||
var usedColors = [];
|
||||
var diff = num - seedColors.length;
|
||||
|
||||
if (diff > 0) {
|
||||
// generate more colors
|
||||
usedColors = _.clone(seedColors);
|
||||
|
||||
for (var newColor, i = 0; i < diff; i++) {
|
||||
|
@ -23,12 +29,9 @@ define(function (require) {
|
|||
usedColors.push(newColor);
|
||||
}
|
||||
} else {
|
||||
// trim to length of array labels
|
||||
usedColors = _.first(seedColors, num);
|
||||
}
|
||||
|
||||
// Returns an array of hex colors
|
||||
// Returned array should be same length as input (num).
|
||||
return usedColors;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
define(function () {
|
||||
/*
|
||||
* Using a random color generator presented awful colors and unpredictable color schemes.
|
||||
* So we needed to come up with one of our own that creates consistent, pleasing color patterns.
|
||||
* So we needed to come up with a color scheme of our own that creates consistent, pleasing color patterns.
|
||||
* The order allows us to guarantee that 1st, 2nd, 3rd, etc values always get the same color.
|
||||
* Returns an array of 72 colors.
|
||||
*/
|
||||
|
||||
return function SeedColorUtilService() {
|
||||
// returns an array of 72 seed colors
|
||||
return [
|
||||
'#57c17b',
|
||||
'#006e8a',
|
||||
|
|
|
@ -4,23 +4,19 @@ define(function (require) {
|
|||
|
||||
var flattenSeries = Private(require('components/vislib/components/labels/flatten_series'));
|
||||
|
||||
/* Takes a kibana obj object
|
||||
* for example:
|
||||
* {
|
||||
* labels: '',
|
||||
* rows: [...],
|
||||
* raw: [...],
|
||||
* ...,
|
||||
* };
|
||||
* Data object can have a key that has rows, columns, or series.
|
||||
/*
|
||||
* Accepts a Kibana data object and returns an array of values objects.
|
||||
*/
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj) || !obj.rows && !obj.columns && !obj.series) {
|
||||
throw new TypeError('GetArrayUtilService expects an object with a series, rows, or columns key');
|
||||
}
|
||||
|
||||
if (!obj.series) {
|
||||
return flattenSeries(obj);
|
||||
}
|
||||
|
||||
// Returns a kibana obj.series array of objects with values array
|
||||
return obj.series;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,25 +2,22 @@ define(function (require) {
|
|||
return function GetSeriesUtilService() {
|
||||
var _ = require('lodash');
|
||||
|
||||
// Accepts a kibana data object
|
||||
/*
|
||||
* Accepts a Kibana data object with a rows or columns key
|
||||
* and returns an array of flattened series values.
|
||||
*/
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj) || !obj.rows && !obj.columns) {
|
||||
throw new TypeError('GetSeriesUtilService expects an object with either a rows or columns key');
|
||||
}
|
||||
|
||||
obj = obj.rows ? obj.rows : obj.columns;
|
||||
|
||||
/*
|
||||
* Flattens the obj.rows or obj.columns arrays
|
||||
* to an array of d.series objects
|
||||
* for example:
|
||||
* [
|
||||
* { label: .., values: [...] },
|
||||
* { label: .., values: [...] },
|
||||
* { label: .., values: [...] }
|
||||
* ]
|
||||
*/
|
||||
|
||||
return _.chain(obj)
|
||||
.pluck('series')
|
||||
.flatten()
|
||||
.value();
|
||||
.pluck('series')
|
||||
.flatten()
|
||||
.value();
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,35 +2,28 @@ define(function (require) {
|
|||
return function LabelUtilService(Private) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var getArr = Private(require('components/vislib/components/labels/data_array'));
|
||||
var createArr = Private(require('components/vislib/components/labels/data_array'));
|
||||
var getArrOfUniqLabels = Private(require('components/vislib/components/labels/uniq_labels'));
|
||||
|
||||
/* Takes a kibana data object
|
||||
* for example:
|
||||
* {
|
||||
* labels: '',
|
||||
* rows: [...],
|
||||
* raw: [...],
|
||||
* ...,
|
||||
* };
|
||||
* Data object can have a key that has rows, columns, or series.
|
||||
/*
|
||||
* Accepts a Kibana data object and returns an array of unique labels (strings).
|
||||
* Extracts the field formatter from the raw object and passes it to the
|
||||
* getArrOfUniqLabels function.
|
||||
*
|
||||
* Currently, this service is only used for vertical bar charts and line charts.
|
||||
*/
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj)) {
|
||||
throw new Error('LabelUtil expects an object');
|
||||
throw new TypeError('LabelUtil expects an object');
|
||||
}
|
||||
|
||||
var raw;
|
||||
var fieldIndex;
|
||||
if (obj.raw) {
|
||||
raw = obj.raw.columns;
|
||||
fieldIndex = _.findIndex(raw, {'categoryName': 'group'});
|
||||
}
|
||||
var raw = obj.raw;
|
||||
var fieldIndex = raw ? _.findIndex(raw, {'categoryName': 'group'}) : undefined;
|
||||
var fieldFormatter = raw && fieldIndex && fieldIndex !== -1 ?
|
||||
raw[fieldIndex].field.format.convert : function (d) { return d; };
|
||||
|
||||
var fieldFormatter = raw && raw[fieldIndex] ? raw[fieldIndex].field.format.convert : function (d) { return d; };
|
||||
|
||||
// Returns an array of unique chart labels
|
||||
return getArrOfUniqLabels(getArr(obj), fieldFormatter);
|
||||
return getArrOfUniqLabels(createArr(obj), fieldFormatter);
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,20 +2,23 @@ define(function (require) {
|
|||
return function UniqLabelUtilService() {
|
||||
var _ = require('lodash');
|
||||
|
||||
// Takes an array of objects
|
||||
/*
|
||||
* Accepts an array of data objects and a formatter function.
|
||||
* Returns a unique list of formatted labels (strings).
|
||||
*/
|
||||
|
||||
return function (arr, formatter) {
|
||||
if (!_.isArray(arr)) {
|
||||
throw TypeError('UniqLabelUtil expects an array of objects');
|
||||
throw new TypeError('UniqLabelUtil expects an array of objects');
|
||||
}
|
||||
|
||||
// Returns a array of unique chart labels
|
||||
return _(arr)
|
||||
.pluck('label')
|
||||
.unique()
|
||||
.map(function (d) {
|
||||
return formatter(d);
|
||||
})
|
||||
.value();
|
||||
.pluck('label')
|
||||
.unique()
|
||||
.map(function (d) {
|
||||
return formatter(d);
|
||||
})
|
||||
.value();
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,27 +2,27 @@ define(function (require) {
|
|||
return function FlattenDataObjectUtilService() {
|
||||
var _ = require('lodash');
|
||||
|
||||
// Takes a kibana data.series array of objects
|
||||
/*
|
||||
* Accepts a Kibana data object, flattens the data.series values array,
|
||||
* and returns an array of values objects.
|
||||
*/
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj) || !obj.rows && !obj.columns && !obj.series) {
|
||||
throw new TypeError('FlattenDataObjUtilService expects an object with a series, rows, or columns key');
|
||||
}
|
||||
|
||||
if (!obj.series) {
|
||||
obj = obj.rows ? obj.rows : obj.columns;
|
||||
|
||||
return _.chain(obj)
|
||||
.pluck('series')
|
||||
.flatten()
|
||||
.pluck('values')
|
||||
.flatten()
|
||||
.value();
|
||||
.pluck('series')
|
||||
.flatten()
|
||||
.pluck('values')
|
||||
.flatten()
|
||||
.value();
|
||||
}
|
||||
|
||||
// Returns an array of objects
|
||||
/*
|
||||
* [
|
||||
* { x: ..., y: ...},
|
||||
* { x: ..., y: ...},
|
||||
* { x: ..., y: ...}
|
||||
* ]
|
||||
*/
|
||||
return _.flatten(obj.series, 'values');
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,44 +6,43 @@ define(function (require) {
|
|||
var createZeroFilledArray = Private(require('components/vislib/components/zero_injection/zero_filled_array'));
|
||||
var zeroFillDataArray = Private(require('components/vislib/components/zero_injection/zero_fill_data_array'));
|
||||
|
||||
// Takes the kibana data objects
|
||||
/*
|
||||
* A Kibana data object may have multiple series with different array lengths.
|
||||
* This proves an impediment to stacking in the visualization library.
|
||||
* Therefore, zero values must be injected wherever these arrays do not line up.
|
||||
* That is, each array must have the same x values with zeros filled in where the
|
||||
* x values were added.
|
||||
*
|
||||
* This function and its helper functions accepts a Kibana data object
|
||||
* and injects zeros where needed.
|
||||
*/
|
||||
|
||||
function getDataArray(obj) {
|
||||
if (obj.rows) {
|
||||
return obj.rows;
|
||||
} else if (obj.columns) {
|
||||
return obj.columns;
|
||||
} else if (obj.series) {
|
||||
return [obj];
|
||||
}
|
||||
}
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj) || !obj.rows && !obj.columns && !obj.series) {
|
||||
throw new TypeError('ZeroInjectionUtilService expects an object with a series, rows, or columns key');
|
||||
}
|
||||
|
||||
var keys = orderXValues(obj);
|
||||
var max;
|
||||
var zeroArray;
|
||||
var dataArray;
|
||||
var i;
|
||||
var j;
|
||||
var arr = getDataArray(obj);
|
||||
|
||||
if (!obj.series) {
|
||||
var arr = obj.rows ? obj.rows : obj.columns;
|
||||
max = arr.length;
|
||||
arr.forEach(function (object) {
|
||||
object.series.forEach(function (series) {
|
||||
var zeroArray = createZeroFilledArray(keys);
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
var jMax = arr[i].series.length;
|
||||
series.values = zeroFillDataArray(zeroArray, series.values);
|
||||
});
|
||||
});
|
||||
|
||||
for (j = 0; j < jMax; j++) {
|
||||
zeroArray = createZeroFilledArray(keys);
|
||||
dataArray = arr[i].series[j].values;
|
||||
arr[i].series[j].values = zeroFillDataArray(zeroArray, dataArray);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Looping thru each arr.values object and replacing
|
||||
// the y value of the zero-filled array
|
||||
max = obj.series.length;
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
zeroArray = createZeroFilledArray(keys);
|
||||
dataArray = obj.series[i].values;
|
||||
|
||||
obj.series[i].values = zeroFillDataArray(zeroArray, dataArray);
|
||||
}
|
||||
|
||||
// Returns a zero-filled array of objects
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,19 +3,25 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var getUniqKeys = Private(require('components/vislib/components/zero_injection/uniq_keys'));
|
||||
|
||||
// Takes a kibana data objects
|
||||
/*
|
||||
* Accepts a Kibana data object and returns
|
||||
* an array of x axis values ordered by their index number.
|
||||
*/
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj)) {
|
||||
throw new Error('OrderedXKeysUtilService expects an object');
|
||||
}
|
||||
|
||||
var objKeys = getUniqKeys(obj);
|
||||
|
||||
// Returns an array x axis values
|
||||
return _.chain(objKeys)
|
||||
.pairs()
|
||||
.sortBy(function (d) {
|
||||
// sort by number
|
||||
if (d[1].isNumber) {
|
||||
// sort by index
|
||||
return +d[0];
|
||||
}
|
||||
return;
|
||||
})
|
||||
.map(function (d) {
|
||||
return d[1].isNumber ? +d[0] : d[0];
|
||||
|
|
|
@ -4,19 +4,37 @@ define(function (require) {
|
|||
|
||||
var flattenDataArray = Private(require('components/vislib/components/zero_injection/flatten_data'));
|
||||
|
||||
// accepts a kibana data.series array of objects
|
||||
/*
|
||||
* Accepts a Kibana data object.
|
||||
* Returns an object with unique x axis values as keys with an object of
|
||||
* their index numbers and an isNumber boolean as their values.
|
||||
* e.g. { 'xAxisValue': { index: 1, isNumber: false }}, ...
|
||||
*/
|
||||
|
||||
return function (obj) {
|
||||
if (!_.isObject(obj)) {
|
||||
throw new TypeError('UniqueXValuesUtilService expects an object');
|
||||
}
|
||||
|
||||
var flattenedData = flattenDataArray(obj);
|
||||
var uniqueXValues = {};
|
||||
|
||||
// Appends unique x values in the order they appear to an empty object
|
||||
flattenedData.forEach(function (d, i) {
|
||||
var key = d.x;
|
||||
uniqueXValues[key] = uniqueXValues[key] === void 0 ?
|
||||
{ index: i, isNumber: _.isNumber(key) } : { index: Math.max(i, uniqueXValues[key].index), isNumber: _.isNumber(key) };
|
||||
|
||||
if (uniqueXValues[key] === void 0) {
|
||||
uniqueXValues[key] = {
|
||||
index: i,
|
||||
isNumber: _.isNumber(key)
|
||||
};
|
||||
} else {
|
||||
uniqueXValues[key] = {
|
||||
index: Math.max(i, uniqueXValues[key].index),
|
||||
isNumber: _.isNumber(key)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// returns an object with unique x values
|
||||
return uniqueXValues;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,13 +2,21 @@ define(function (require) {
|
|||
return function ZeroFillDataArrayUtilService(Private) {
|
||||
var _ = require('lodash');
|
||||
|
||||
// Accepts an array of zero-filled y value objects
|
||||
// and a kibana data.series[i].values array of objects
|
||||
/*
|
||||
* Accepts an array of zero-filled y value objects (arr1)
|
||||
* and a kibana data.series[i].values array of objects (arr2).
|
||||
* Return a zero-filled array of objects (arr1).
|
||||
*/
|
||||
|
||||
return function (arr1, arr2) {
|
||||
if (!_.isArray(arr1) || !_.isArray(arr2)) {
|
||||
throw new TypeError('ZeroFillDataArrayUtilService expects 2 arrays');
|
||||
}
|
||||
|
||||
var max = arr2.length;
|
||||
var getX = function (d) {
|
||||
return d.x === val.x;
|
||||
};
|
||||
var max = arr2.length;
|
||||
var i;
|
||||
var val;
|
||||
var index;
|
||||
|
@ -19,7 +27,6 @@ define(function (require) {
|
|||
arr1.splice(index, 1, val);
|
||||
}
|
||||
|
||||
// Return a zero-filled array of objects
|
||||
return arr1;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
define(function () {
|
||||
return function ZeroFilledArrayUtilService() {
|
||||
// Accepts an array of strings or numbers
|
||||
// and a kibana data.ordered object
|
||||
var _ = require('lodash');
|
||||
|
||||
/*
|
||||
* Accepts an array of x axis values (strings or numbers).
|
||||
* Returns a zero filled array.
|
||||
*/
|
||||
|
||||
return function (arr) {
|
||||
var max = arr.length;
|
||||
var i;
|
||||
var val;
|
||||
if (!_.isArray(arr)) {
|
||||
throw new Error('ZeroFilledArrayUtilService expects an array of strings or numbers');
|
||||
}
|
||||
|
||||
var zeroFilledArray = [];
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
val = arr[i];
|
||||
|
||||
arr.forEach(function (val) {
|
||||
zeroFilledArray.push({
|
||||
x: val,
|
||||
y: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Returns an array of objects with y value of 0
|
||||
return zeroFilledArray;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,7 +7,13 @@ define(function (require) {
|
|||
return require('d3');
|
||||
});
|
||||
|
||||
// Kibana visualization library
|
||||
/**
|
||||
* Provides the Kibana4 Visualization Library
|
||||
*
|
||||
* @module visLib
|
||||
* @main visLib
|
||||
* @return {Object} Contains the version number and the Vis Class for creating visualizations
|
||||
*/
|
||||
module.service('visLib', function (Private) {
|
||||
return {
|
||||
version: '0.0.0',
|
||||
|
|
|
@ -2,13 +2,26 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
return function ErrorHandlerFactory(Private) {
|
||||
// Common errors shared between constructors
|
||||
return function ErrorHandlerFactory() {
|
||||
|
||||
/**
|
||||
* Common errors shared between constructors
|
||||
*
|
||||
* @class ErrorHandler
|
||||
* @constructor
|
||||
*/
|
||||
function ErrorHandler() {}
|
||||
|
||||
// Validate the height and width are > 0
|
||||
/**
|
||||
* Validates the height and width are > 0
|
||||
* min size must be at least 1 px
|
||||
*
|
||||
* @method validateWidthandHeight
|
||||
* @param width {Number} HTMLElement width
|
||||
* @param height {Number} HTMLElement height
|
||||
* @returns {HTMLElement} HTML div with an error message
|
||||
*/
|
||||
ErrorHandler.prototype.validateWidthandHeight = function (width, height) {
|
||||
// min size must be at least 1px
|
||||
var badWidth = _.isNaN(width) || width <= 0;
|
||||
var badHeight = _.isNaN(height) || height <= 0;
|
||||
|
||||
|
|
|
@ -5,8 +5,14 @@ define(function (require) {
|
|||
|
||||
var ErrorHandler = Private(require('components/vislib/lib/_error_handler'));
|
||||
|
||||
/*
|
||||
/**
|
||||
* Appends axis title(s) to the visualization
|
||||
*
|
||||
* @class AxisTitle
|
||||
* @constructor
|
||||
* @param el {HTMLElement} DOM element
|
||||
* @param xTitle {String} X-axis title
|
||||
* @param yTitle {String} Y-axis title
|
||||
*/
|
||||
function AxisTitle(el, xTitle, yTitle) {
|
||||
if (!(this instanceof AxisTitle)) {
|
||||
|
@ -20,13 +26,24 @@ define(function (require) {
|
|||
|
||||
_(AxisTitle.prototype).extend(ErrorHandler.prototype);
|
||||
|
||||
// Render both x and y axis titles
|
||||
/**
|
||||
* Renders both x and y axis titles
|
||||
*
|
||||
* @method render
|
||||
* @returns {HTMLElement} DOM Element with axis titles
|
||||
*/
|
||||
AxisTitle.prototype.render = function () {
|
||||
d3.select(this.el).select('.x-axis-title').call(this.draw(this.xTitle));
|
||||
d3.select(this.el).select('.y-axis-title').call(this.draw(this.yTitle));
|
||||
};
|
||||
|
||||
// Return a callback function that appends an svg with title text
|
||||
/**
|
||||
* Appends an SVG with title text
|
||||
*
|
||||
* @method draw
|
||||
* @param title {String} Axis title
|
||||
* @returns {Function} Appends axis title to a D3 selection
|
||||
*/
|
||||
AxisTitle.prototype.draw = function (title) {
|
||||
var self = this;
|
||||
|
||||
|
@ -43,14 +60,14 @@ define(function (require) {
|
|||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.append('text')
|
||||
.attr('transform', function () {
|
||||
if (div.attr('class') === 'x-axis-title') {
|
||||
return 'translate(' + width / 2 + ',11)';
|
||||
}
|
||||
return 'translate(11,' + height / 2 + ')rotate(270)';
|
||||
})
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(title);
|
||||
.attr('transform', function () {
|
||||
if (div.attr('class') === 'x-axis-title') {
|
||||
return 'translate(' + width / 2 + ',11)';
|
||||
}
|
||||
return 'translate(11,' + height / 2 + ')rotate(270)';
|
||||
})
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(title);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,11 +6,14 @@ define(function (require) {
|
|||
var ErrorHandler = Private(require('components/vislib/lib/_error_handler'));
|
||||
var Tooltip = Private(require('components/vislib/lib/tooltip'));
|
||||
|
||||
/*
|
||||
* Append chart titles to the visualization
|
||||
* arguments:
|
||||
* el => reference to a DOM element
|
||||
/**
|
||||
* Appends chart titles to the visualization
|
||||
*
|
||||
* @class ChartTitle
|
||||
* @constructor
|
||||
* @param el {HTMLElement} Reference to DOM element
|
||||
*/
|
||||
|
||||
function ChartTitle(el) {
|
||||
if (!(this instanceof ChartTitle)) {
|
||||
return new ChartTitle(el);
|
||||
|
@ -24,12 +27,23 @@ define(function (require) {
|
|||
|
||||
_(ChartTitle.prototype).extend(ErrorHandler.prototype);
|
||||
|
||||
// Render chart titles
|
||||
/**
|
||||
* Renders chart titles
|
||||
*
|
||||
* @method render
|
||||
* @returns {D3.Selection|D3.Transition.Transition} DOM element with chart titles
|
||||
*/
|
||||
ChartTitle.prototype.render = function () {
|
||||
return d3.select(this.el).selectAll('.chart-title').call(this.draw());
|
||||
};
|
||||
|
||||
// Return a function that truncates chart title text
|
||||
/**
|
||||
* Truncates chart title text
|
||||
*
|
||||
* @method truncate
|
||||
* @param size {Number} Height or width of the HTML Element
|
||||
* @returns {Function} Truncates text
|
||||
*/
|
||||
ChartTitle.prototype.truncate = function (size) {
|
||||
var self = this;
|
||||
|
||||
|
@ -47,10 +61,7 @@ define(function (require) {
|
|||
str = text.text();
|
||||
avg = length / str.length;
|
||||
end = Math.floor(maxWidth / avg) - 5;
|
||||
|
||||
str = str.substr(0, end) + '...';
|
||||
|
||||
// mouseover and mouseout
|
||||
self.addMouseEvents(text);
|
||||
|
||||
return text.text(str);
|
||||
|
@ -61,14 +72,25 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Add mouseover and mouseout events on truncated chart titles
|
||||
/**
|
||||
* Adds tooltip events on truncated chart titles
|
||||
*
|
||||
* @method addMouseEvents
|
||||
* @param target {HTMLElement} DOM element to attach event listeners
|
||||
* @returns {*} DOM element with event listeners attached
|
||||
*/
|
||||
ChartTitle.prototype.addMouseEvents = function (target) {
|
||||
if (this.tooltip) {
|
||||
return target.call(this.tooltip.render());
|
||||
}
|
||||
};
|
||||
|
||||
// Return a callback function that appends chart titles to the visualization
|
||||
/**
|
||||
* Appends chart titles to the visualization
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Appends chart titles to a D3 selection
|
||||
*/
|
||||
ChartTitle.prototype.draw = function () {
|
||||
var self = this;
|
||||
|
||||
|
@ -81,32 +103,31 @@ define(function (require) {
|
|||
var size = dataType === 'rows' ? height : width;
|
||||
var txtHtOffset = 11;
|
||||
|
||||
// Check if width or height are 0 or NaN
|
||||
self.validateWidthandHeight(width, height);
|
||||
|
||||
div.append('svg')
|
||||
.attr('width', function () {
|
||||
if (dataType === 'rows') {
|
||||
return 15;
|
||||
}
|
||||
return width;
|
||||
})
|
||||
.attr('height', height)
|
||||
.append('text')
|
||||
.attr('transform', function () {
|
||||
if (dataType === 'rows') {
|
||||
// if `rows`, rotate the chart titles
|
||||
return 'translate(' + txtHtOffset + ',' + height / 2 + ')rotate(270)';
|
||||
}
|
||||
return 'translate(' + width / 2 + ',' + txtHtOffset + ')';
|
||||
})
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(function (d) {
|
||||
return d.label;
|
||||
});
|
||||
.attr('width', function () {
|
||||
if (dataType === 'rows') {
|
||||
return 15;
|
||||
}
|
||||
return width;
|
||||
})
|
||||
.attr('height', height)
|
||||
.append('text')
|
||||
.attr('transform', function () {
|
||||
if (dataType === 'rows') {
|
||||
return 'translate(' + txtHtOffset + ',' + height / 2 + ')rotate(270)';
|
||||
}
|
||||
return 'translate(' + width / 2 + ',' + txtHtOffset + ')';
|
||||
})
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(function (d) {
|
||||
return d.label;
|
||||
});
|
||||
|
||||
// truncate long chart titles
|
||||
div.selectAll('text').call(self.truncate(size));
|
||||
div.selectAll('text')
|
||||
.call(self.truncate(size));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,10 +7,14 @@ define(function (require) {
|
|||
var getLabels = Private(require('components/vislib/components/labels/labels'));
|
||||
var color = Private(require('components/vislib/components/color/color'));
|
||||
|
||||
/*
|
||||
/**
|
||||
* Provides an API for pulling values off the data
|
||||
* arguments:
|
||||
* data => Provided data object
|
||||
* and calculating values using the data
|
||||
*
|
||||
* @class Data
|
||||
* @constructor
|
||||
* @param data {Object} Elasticsearch query results
|
||||
* @param attr {Object|*} Visualization options
|
||||
*/
|
||||
function Data(data, attr) {
|
||||
if (!(this instanceof Data)) {
|
||||
|
@ -18,8 +22,9 @@ define(function (require) {
|
|||
}
|
||||
|
||||
this.data = data;
|
||||
this._normalizeOrdered();
|
||||
|
||||
this._attr = attr;
|
||||
// d3 stack function
|
||||
this._attr = _.defaults(attr || {}, {
|
||||
offset: 'zero',
|
||||
stack: d3.layout.stack()
|
||||
|
@ -29,7 +34,13 @@ define(function (require) {
|
|||
});
|
||||
}
|
||||
|
||||
// Return the actual x and y data values
|
||||
/**
|
||||
* Returns an array of the actual x and y data value objects
|
||||
* from data with series keys
|
||||
*
|
||||
* @method chartData
|
||||
* @returns {*} Array of data objects
|
||||
*/
|
||||
Data.prototype.chartData = function () {
|
||||
if (!this.data.series) {
|
||||
var arr = this.data.rows ? this.data.rows : this.data.columns;
|
||||
|
@ -38,6 +49,12 @@ define(function (require) {
|
|||
return [this.data];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of chart data objects
|
||||
*
|
||||
* @method getVisData
|
||||
* @returns {*} Array of chart data objects
|
||||
*/
|
||||
Data.prototype.getVisData = function () {
|
||||
var visData;
|
||||
|
||||
|
@ -52,8 +69,13 @@ define(function (require) {
|
|||
return visData;
|
||||
};
|
||||
|
||||
// Function to determine whether to display the legend or not
|
||||
// Displays legend when more than one series of data present
|
||||
/**
|
||||
* Function to determine whether to display the legend or not
|
||||
* Displays legend when more than one series of data present
|
||||
*
|
||||
* @method isLegendShown
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Data.prototype.isLegendShown = function () {
|
||||
var isLegend = false;
|
||||
var visData = this.getVisData();
|
||||
|
@ -62,7 +84,7 @@ define(function (require) {
|
|||
|
||||
_.forEach(visData, function countSeriesLength(obj) {
|
||||
var dataLength = obj.series ? obj.series.length : obj.slices.children.length;
|
||||
var label = dataLength === 1 && obj.series ? obj.series[0].label : undefined;
|
||||
var label = (dataLength === 1 && obj.series) ? obj.series[0].label : undefined;
|
||||
|
||||
if (!seriesLabel) {
|
||||
seriesLabel = label;
|
||||
|
@ -80,6 +102,12 @@ define(function (require) {
|
|||
return isLegend;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns array of chart data objects for pie data objects
|
||||
*
|
||||
* @method pieData
|
||||
* @returns {*} Array of chart data objects
|
||||
*/
|
||||
Data.prototype.pieData = function () {
|
||||
if (!this.data.slices) {
|
||||
return this.data.rows ? this.data.rows : this.data.columns;
|
||||
|
@ -87,7 +115,16 @@ define(function (require) {
|
|||
return [this.data];
|
||||
};
|
||||
|
||||
// Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter`
|
||||
/**
|
||||
* Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter`
|
||||
* pulls the value off the first item in the array
|
||||
* these values are typically the same between data objects of the same chart
|
||||
* TODO: May need to verify this or refactor
|
||||
*
|
||||
* @method get
|
||||
* @param thing {String} Data object key
|
||||
* @returns {*} Data object value
|
||||
*/
|
||||
Data.prototype.get = function (thing) {
|
||||
var data;
|
||||
|
||||
|
@ -99,43 +136,55 @@ define(function (require) {
|
|||
data = [this.data];
|
||||
}
|
||||
|
||||
// pulls the value off the first item in the array
|
||||
// these values are typically the same between data objects of the same chart
|
||||
// May need to verify this or refactor
|
||||
return _.pluck(data, thing)[0];
|
||||
};
|
||||
|
||||
// Return an array of all value objects
|
||||
/**
|
||||
* Return an array of all value objects
|
||||
* Pluck the data.series array from each data object
|
||||
* Create an array of all the value objects from the series array
|
||||
*
|
||||
* @method flatten
|
||||
* @returns {Array} Value objects
|
||||
*/
|
||||
Data.prototype.flatten = function () {
|
||||
var data = this.chartData();
|
||||
// Pluck the data.series array from each data object
|
||||
var series = _.chain(data).pluck('series').pluck().value();
|
||||
var values = [];
|
||||
|
||||
// Create an array of all the value objects from the series array
|
||||
_(series).forEach(function (d) {
|
||||
series.forEach(function (d) {
|
||||
values.push(_.chain(d).flatten().pluck('values').value());
|
||||
});
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
// TODO: need to make this more generic
|
||||
/**
|
||||
* Determines whether histogram charts should be stacked
|
||||
* TODO: need to make this more generic
|
||||
*
|
||||
* @method shouldBeStacked
|
||||
* @param series {Array} Array of data objects
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Data.prototype.shouldBeStacked = function (series) {
|
||||
// Series should be an array
|
||||
if (this._attr.type === 'histogram' && series.length > 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return (this._attr.type === 'histogram' && series.length > 1);
|
||||
};
|
||||
|
||||
// Calculate the max y value from this.dataArray
|
||||
/**
|
||||
* Calculate the max y value from this.dataArray
|
||||
* for each object in the dataArray,
|
||||
* push the calculated y value to the initialized array (arr)
|
||||
* return the largest value from the array
|
||||
*
|
||||
* @method getYMaxValue
|
||||
* @returns {Number} Max y axis value
|
||||
*/
|
||||
Data.prototype.getYMaxValue = function () {
|
||||
var self = this;
|
||||
var arr = [];
|
||||
|
||||
// for each object in the dataArray,
|
||||
// push the calculated y value to the initialized array (arr)
|
||||
_.forEach(this.flatten(), function (series) {
|
||||
if (self.shouldBeStacked(series)) {
|
||||
return arr.push(self.getYStackMax(series));
|
||||
|
@ -143,14 +192,27 @@ define(function (require) {
|
|||
return arr.push(self.getYMax(series));
|
||||
});
|
||||
|
||||
// return the largest value from the array
|
||||
return _.max(arr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the stacked values for each data object
|
||||
*
|
||||
* @method stackData
|
||||
* @param series {Array} Array of data objects
|
||||
* @returns {*} Array of data objects with x, y, y0 keys
|
||||
*/
|
||||
Data.prototype.stackData = function (series) {
|
||||
return this._attr.stack(series);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the largest y stack value among all data objects
|
||||
*
|
||||
* @method getYStackMax
|
||||
* @param series {Array} Array of data objects
|
||||
* @returns {Number} Y stack max value
|
||||
*/
|
||||
Data.prototype.getYStackMax = function (series) {
|
||||
return d3.max(this.stackData(series), function (data) {
|
||||
return d3.max(data, function (d) {
|
||||
|
@ -159,6 +221,13 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the Y domain max value
|
||||
*
|
||||
* @method getMax
|
||||
* @param series {Array} Array of data objects
|
||||
* @returns {Number} Y domain max value
|
||||
*/
|
||||
Data.prototype.getYMax = function (series) {
|
||||
return d3.max(series, function (data) {
|
||||
return d3.max(data, function (d) {
|
||||
|
@ -167,15 +236,23 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
// Helper function for getNames
|
||||
// Returns an array of objects with a name (key) value and an index value.
|
||||
// The index value allows us to sort the names in the correct nested order.
|
||||
/**
|
||||
* Helper function for getNames
|
||||
* Returns an array of objects with a name (key) value and an index value.
|
||||
* The index value allows us to sort the names in the correct nested order.
|
||||
*
|
||||
* @method returnNames
|
||||
* @param array {Array} Array of data objects
|
||||
* @param index {Number} Number of times the object is nested
|
||||
* @param columns {Object} Contains name formatter information
|
||||
* @returns {Array} Array of labels (strings)
|
||||
*/
|
||||
Data.prototype.returnNames = function (array, index, columns) {
|
||||
var names = [];
|
||||
var self = this;
|
||||
|
||||
_.forEach(array, function (obj) {
|
||||
var fieldFormatter = columns && columns[index].field ? columns[index].field.format.convert : function (d) { return d; };
|
||||
var fieldFormatter = (columns && columns[index].field) ? columns[index].field.format.convert : function (d) { return d; };
|
||||
names.push({ key: fieldFormatter(obj.name), index: index });
|
||||
|
||||
if (obj.children) {
|
||||
|
@ -190,9 +267,16 @@ define(function (require) {
|
|||
return names;
|
||||
};
|
||||
|
||||
// Flattens hierarchical data into an array of objects with a name and index value.
|
||||
// The indexed value determines the order of nesting in the data.
|
||||
// Returns an array with names sorted by the index value.
|
||||
/**
|
||||
* Flattens hierarchical data into an array of objects with a name and index value.
|
||||
* The indexed value determines the order of nesting in the data.
|
||||
* Returns an array with names sorted by the index value.
|
||||
*
|
||||
* @method getNames
|
||||
* @param data {Object} Chart data object
|
||||
* @param columns {Object} Contains formatter information
|
||||
* @returns {Array} Array of names (strings)
|
||||
*/
|
||||
Data.prototype.getNames = function (data, columns) {
|
||||
var slices = data.slices;
|
||||
|
||||
|
@ -200,15 +284,22 @@ define(function (require) {
|
|||
var namedObj = this.returnNames(slices.children, 0, columns);
|
||||
|
||||
return _(namedObj)
|
||||
.sortBy(function (obj) {
|
||||
return obj.index;
|
||||
})
|
||||
.pluck('key')
|
||||
.unique()
|
||||
.value();
|
||||
.sortBy(function (obj) {
|
||||
return obj.index;
|
||||
})
|
||||
.pluck('key')
|
||||
.unique()
|
||||
.value();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of names ordered by appearance in the nested array
|
||||
* of objects
|
||||
*
|
||||
* @method pieNames
|
||||
* @returns {Array} Array of unique names (strings)
|
||||
*/
|
||||
Data.prototype.pieNames = function () {
|
||||
var self = this;
|
||||
var names = [];
|
||||
|
@ -224,31 +315,77 @@ define(function (require) {
|
|||
return _.uniq(names);
|
||||
};
|
||||
|
||||
// Inject zeros into the data
|
||||
/**
|
||||
* Inject zeros into the data
|
||||
*
|
||||
* @method injectZeros
|
||||
* @returns {Object} Data object with zeros injected
|
||||
*/
|
||||
Data.prototype.injectZeros = function () {
|
||||
return injectZeros(this.data);
|
||||
};
|
||||
|
||||
// Return an array of all x item values from the data
|
||||
/**
|
||||
* Returns an array of all x axis values from the data
|
||||
*
|
||||
* @method xValues
|
||||
* @returns {Array} Array of x axis values
|
||||
*/
|
||||
Data.prototype.xValues = function () {
|
||||
return orderKeys(this.data);
|
||||
};
|
||||
|
||||
// Return an array of unique labels
|
||||
/**
|
||||
* Return an array of unique labels
|
||||
* Curently, only used for vertical bar and line charts,
|
||||
* or any data object with series values
|
||||
*
|
||||
* @method getLabels
|
||||
* @returns {Array} Array of labels (strings)
|
||||
*/
|
||||
Data.prototype.getLabels = function () {
|
||||
return getLabels(this.data);
|
||||
};
|
||||
|
||||
// Return a function that does color lookup on labels
|
||||
/**
|
||||
* Returns a function that does color lookup on labels
|
||||
*
|
||||
* @method getColorFunc
|
||||
* @returns {Function} Performs lookup on string and returns hex color
|
||||
*/
|
||||
Data.prototype.getColorFunc = function () {
|
||||
return color(this.getLabels());
|
||||
};
|
||||
|
||||
// Return a function that does color lookup on names for pie charts
|
||||
/**
|
||||
* Returns a function that does color lookup on names for pie charts
|
||||
*
|
||||
* @method getPieColorFunc
|
||||
* @returns {Function} Performs lookup on string and returns hex color
|
||||
*/
|
||||
Data.prototype.getPieColorFunc = function () {
|
||||
return color(this.pieNames());
|
||||
};
|
||||
|
||||
/**
|
||||
* ensure that the datas ordered property has a min and max
|
||||
* if the data represents an ordered date range.
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
Data.prototype._normalizeOrdered = function () {
|
||||
if (!this.data.ordered || !this.data.ordered.date) return;
|
||||
|
||||
var missingMin = this.data.ordered.min == null;
|
||||
var missingMax = this.data.ordered.max == null;
|
||||
|
||||
if (missingMax || missingMin) {
|
||||
var extent = d3.extent(this.xValues());
|
||||
if (missingMin) this.data.ordered.min = extent[0];
|
||||
if (missingMax) this.data.ordered.max = extent[1];
|
||||
}
|
||||
};
|
||||
|
||||
return Data;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
define(function (require) {
|
||||
return function DispatchClass(d3, Private) {
|
||||
return function DispatchClass(d3) {
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Events Class
|
||||
* Handles event responses
|
||||
*
|
||||
* @class Dispatch
|
||||
* @constructor
|
||||
* @param handler {Object} Reference to Handler Class Object
|
||||
* @param chartData {Object} Elasticsearch data object
|
||||
*/
|
||||
|
||||
function Dispatch(handler, chartData) {
|
||||
if (!(this instanceof Dispatch)) {
|
||||
return new Dispatch(handler, chartData);
|
||||
|
@ -22,7 +28,14 @@ define(function (require) {
|
|||
});
|
||||
}
|
||||
|
||||
// Response to `click` and `hover` events
|
||||
/**
|
||||
* Response to click and hover events
|
||||
*
|
||||
* @param d {Object} Data point
|
||||
* @param i {Number} Index number of data point
|
||||
* @returns {{value: *, point: *, label: *, color: *, pointIndex: *, series: *, config: *, data: (Object|*),
|
||||
* e: (d3.event|*), handler: (Object|*)}} Event response object
|
||||
*/
|
||||
Dispatch.prototype.eventResponse = function (d, i) {
|
||||
var label = d.label;
|
||||
var getYValue = this._attr.yValue;
|
||||
|
@ -45,7 +58,14 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Pie response to `click` and `hover` events
|
||||
/**
|
||||
* Response to click and hover events for pie charts
|
||||
*
|
||||
* @param d {Object} Data point
|
||||
* @param i {Number} Index number of data point
|
||||
* @returns {{value: (d.value|*), point: *, label: (d.name|*), color: *, pointIndex: *, children: *, parent: *,
|
||||
* appConfig: *, config: *, data: (Object|*), e: (d3.event|*), handler: (Object|*)}} Event response object
|
||||
*/
|
||||
Dispatch.prototype.pieResponse = function (d, i) {
|
||||
var label = d.name;
|
||||
var color = this.color;
|
||||
|
@ -69,7 +89,13 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Add brush to the svg
|
||||
/**
|
||||
* Adds D3 brush to SVG and returns the brush function
|
||||
*
|
||||
* @param xScale {Function} D3 xScale function
|
||||
* @param svg {HTMLElement} Reference to SVG
|
||||
* @returns {*} Returns a D3 brush function and a SVG with a brush group attached
|
||||
*/
|
||||
Dispatch.prototype.addBrush = function (xScale, svg) {
|
||||
var dispatch = this._attr.dispatch;
|
||||
var attr = this._attr;
|
||||
|
@ -94,10 +120,10 @@ define(function (require) {
|
|||
// if `addBrushing` is true, add brush canvas
|
||||
if (isBrush) {
|
||||
svg.append('g')
|
||||
.attr('class', 'brush')
|
||||
.call(brush)
|
||||
.selectAll('rect')
|
||||
.attr('height', height - margin.top - margin.bottom);
|
||||
.attr('class', 'brush')
|
||||
.call(brush)
|
||||
.selectAll('rect')
|
||||
.attr('height', height - margin.top - margin.bottom);
|
||||
}
|
||||
|
||||
return brush;
|
||||
|
|
|
@ -5,13 +5,14 @@ define(function (require) {
|
|||
var Data = Private(require('components/vislib/lib/data'));
|
||||
var Layout = Private(require('components/vislib/lib/layout/layout'));
|
||||
|
||||
/*
|
||||
/**
|
||||
* Handles building all the components of the visualization
|
||||
* arguments:
|
||||
* vis => this object from vis.js
|
||||
*
|
||||
* returns an object with reference to the vis.prototype,
|
||||
* and news up all the constructors needed to build a visualization
|
||||
* @class Handler
|
||||
* @constructor
|
||||
* @param vis {Object} Reference to the Vis Class Constructor
|
||||
* @param opts {Object} Reference to Visualization constructors needed to
|
||||
* create the visualization
|
||||
*/
|
||||
function Handler(vis, opts) {
|
||||
if (!(this instanceof Handler)) {
|
||||
|
@ -26,7 +27,6 @@ define(function (require) {
|
|||
'margin' : { top: 10, right: 3, bottom: 5, left: 3 }
|
||||
});
|
||||
|
||||
// Visualization constructors
|
||||
this.layout = new Layout(vis.el, vis.data, vis._attr.type);
|
||||
this.xAxis = opts.xAxis;
|
||||
this.yAxis = opts.yAxis;
|
||||
|
@ -37,11 +37,9 @@ define(function (require) {
|
|||
this.legend = opts.legend;
|
||||
}
|
||||
|
||||
// Array of objects to render to the visualization
|
||||
this.renderArray = _.filter([
|
||||
this.layout,
|
||||
this.legend,
|
||||
this.tooltip,
|
||||
this.axisTitle,
|
||||
this.chartTitle,
|
||||
this.xAxis,
|
||||
|
@ -49,27 +47,28 @@ define(function (require) {
|
|||
], Boolean);
|
||||
}
|
||||
|
||||
// Render the visualization
|
||||
/**
|
||||
* Renders the constructors that create the visualization,
|
||||
* including the chart constructor
|
||||
*
|
||||
* @method render
|
||||
* @returns {HTMLElement} With the visualization child element
|
||||
*/
|
||||
Handler.prototype.render = function () {
|
||||
var self = this;
|
||||
// Save a reference to the charts
|
||||
var charts = this.charts = [];
|
||||
|
||||
// Render objects in the render array
|
||||
_.forEach(this.renderArray, function (property) {
|
||||
if (typeof property.render === 'function') {
|
||||
property.render();
|
||||
}
|
||||
});
|
||||
|
||||
// Add charts to the visualization
|
||||
d3.select(this.el)
|
||||
.selectAll('.chart')
|
||||
.each(function (chartData) {
|
||||
// new up the visualization type
|
||||
var chart = new self.ChartClass(self, this, chartData);
|
||||
|
||||
// Bind events to the chart
|
||||
d3.rebind(chart, chart._attr.dispatch, 'on');
|
||||
|
||||
// Bubble events up to the Vis Class and Events Class
|
||||
|
@ -85,30 +84,41 @@ define(function (require) {
|
|||
self.vis.emit('brush', e);
|
||||
});
|
||||
|
||||
// Save reference to charts
|
||||
charts.push(chart);
|
||||
|
||||
// Render charts to screen
|
||||
chart.render();
|
||||
});
|
||||
};
|
||||
|
||||
// Remove all DOM elements from the `el` provided
|
||||
/**
|
||||
* Removes all DOM elements from the HTML element provided
|
||||
*
|
||||
* @method removeAll
|
||||
* @param el {HTMLElement} Reference to the HTML Element that
|
||||
* contains the chart
|
||||
* @returns {D3.Selection|D3.Transition.Transition} With the chart
|
||||
* child element removed
|
||||
*/
|
||||
Handler.prototype.removeAll = function (el) {
|
||||
return d3.select(el).selectAll('*').remove();
|
||||
};
|
||||
|
||||
// Display an error message on the screen
|
||||
/**
|
||||
* Displays an error message in the DOM
|
||||
*
|
||||
* @method error
|
||||
* @param message {String} Error message to display
|
||||
* @returns {HTMLElement} Displays the input message
|
||||
*/
|
||||
Handler.prototype.error = function (message) {
|
||||
this.removeAll(this.el);
|
||||
|
||||
// Return an error wrapper DOM element
|
||||
return d3.select(this.el).append('div')
|
||||
// class name needs `chart` in it for the polling checkSize function
|
||||
// to continuously call render on resize
|
||||
.attr('class', 'chart error')
|
||||
.append('p')
|
||||
.text(message);
|
||||
return d3.select(this.el)
|
||||
.append('div')
|
||||
// class name needs `chart` in it for the polling checkSize function
|
||||
// to continuously call render on resize
|
||||
.attr('class', 'chart error')
|
||||
.append('p')
|
||||
.text(message);
|
||||
};
|
||||
|
||||
return Handler;
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
define(function (require) {
|
||||
return function HandlerTypeFactory(Private) {
|
||||
// handler types
|
||||
|
||||
/**
|
||||
* Handles the building of each visualization
|
||||
*
|
||||
* @return {Function} Returns an Object of Handler types
|
||||
*/
|
||||
return {
|
||||
histogram: Private(require('components/vislib/lib/handler/types/column')),
|
||||
line: Private(require('components/vislib/lib/handler/types/column')),
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
define(function (require) {
|
||||
return function ColumnHandler(d3, Private) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var injectZeros = Private(require('components/vislib/components/zero_injection/inject_zeros'));
|
||||
var Handler = Private(require('components/vislib/lib/handler/handler'));
|
||||
var Data = Private(require('components/vislib/lib/data'));
|
||||
|
@ -11,10 +9,15 @@ define(function (require) {
|
|||
var AxisTitle = Private(require('components/vislib/lib/axis_title'));
|
||||
var ChartTitle = Private(require('components/vislib/lib/chart_title'));
|
||||
|
||||
/*
|
||||
* Handler for Column Chart, Line Chart, and other charts using x and y axes.
|
||||
* Otherwise known as the default Handler type.
|
||||
*/
|
||||
|
||||
return function (vis) {
|
||||
var data = new Data(injectZeros(vis.data), vis._attr);
|
||||
|
||||
var ColumnHandler = new Handler(vis, {
|
||||
return new Handler(vis, {
|
||||
data: data,
|
||||
legend: new Legend(vis, vis.el, data.getLabels(), data.getColorFunc(), vis._attr),
|
||||
axisTitle: new AxisTitle(vis.el, data.get('xAxisLabel'), data.get('yAxisLabel')),
|
||||
|
@ -32,8 +35,6 @@ define(function (require) {
|
|||
_attr: vis._attr
|
||||
})
|
||||
});
|
||||
|
||||
return ColumnHandler;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
define(function (require) {
|
||||
return function PieHandler(d3, Private) {
|
||||
|
||||
var Handler = Private(require('components/vislib/lib/handler/handler'));
|
||||
var Data = Private(require('components/vislib/lib/data'));
|
||||
var Legend = Private(require('components/vislib/lib/legend'));
|
||||
var ChartTitle = Private(require('components/vislib/lib/chart_title'));
|
||||
|
||||
/*
|
||||
* Handler for Pie visualizations.
|
||||
*/
|
||||
|
||||
return function (vis) {
|
||||
var data = new Data(vis.data, vis._attr);
|
||||
|
||||
var PieHandler = new Handler(vis, {
|
||||
return new Handler(vis, {
|
||||
legend: new Legend(vis, vis.el, data.pieNames(), data.getPieColorFunc(), vis._attr),
|
||||
chartTitle: new ChartTitle(vis.el)
|
||||
});
|
||||
|
||||
return PieHandler;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -4,14 +4,22 @@ define(function (require) {
|
|||
|
||||
var layoutType = Private(require('components/vislib/lib/layout/layout_types'));
|
||||
|
||||
/*
|
||||
* The Layout Constructor is responsible for rendering the visualization
|
||||
* layout, which includes all the DOM div elements.
|
||||
* Input:
|
||||
* 1. DOM div - parent element for which the layout is attached
|
||||
* 2. data - data is bound to the div element
|
||||
* 3. chartType (e.g. 'histogram') - specifies the layout type to grab
|
||||
*/
|
||||
/**
|
||||
* Builds the visualization DOM layout
|
||||
*
|
||||
* The Layout Constructor is responsible for rendering the visualization
|
||||
* layout, which includes all the DOM div elements.
|
||||
* Input:
|
||||
* 1. DOM div - parent element for which the layout is attached
|
||||
* 2. data - data is bound to the div element
|
||||
* 3. chartType (e.g. 'histogram') - specifies the layout type to grab
|
||||
*
|
||||
* @class Layout
|
||||
* @constructor
|
||||
* @param el {HTMLElement} HTML element to which the chart will be appended
|
||||
* @param data {Object} Elasticsearch query results for this specific chart
|
||||
* @param chartType {Object} Reference to chart functions, i.e. Pie
|
||||
*/
|
||||
function Layout(el, data, chartType) {
|
||||
if (!(this instanceof Layout)) {
|
||||
return new Layout(el, data, chartType);
|
||||
|
@ -23,25 +31,42 @@ define(function (require) {
|
|||
}
|
||||
|
||||
// Render the layout
|
||||
/**
|
||||
* Renders visualization HTML layout
|
||||
* Remove all elements from the current visualization and creates the layout
|
||||
*
|
||||
* @method render
|
||||
*/
|
||||
Layout.prototype.render = function () {
|
||||
// Remove all elements from the current visualization
|
||||
this.removeAll(this.el);
|
||||
|
||||
// Create the layout
|
||||
this.createLayout(this.layoutType);
|
||||
};
|
||||
|
||||
// Create the layout based on the json array provided
|
||||
/**
|
||||
* Create the layout based on the json array provided
|
||||
* for each object in the layout array, call the layout function
|
||||
*
|
||||
* @method createLayout
|
||||
* @param arr {Array} Json array
|
||||
* @returns {*} Creates the visualization layout
|
||||
*/
|
||||
Layout.prototype.createLayout = function (arr) {
|
||||
var self = this;
|
||||
|
||||
// for each object in the layout array, call the layout function
|
||||
return _(arr).forEach(function (obj) {
|
||||
self.layout(obj);
|
||||
});
|
||||
};
|
||||
|
||||
// Appends a DOM element based on the object keys
|
||||
/**
|
||||
* Appends a DOM element based on the object keys
|
||||
* check to see if reference to DOM element is string but not class selector
|
||||
* Create a class selector
|
||||
*
|
||||
* @method layout
|
||||
* @param obj {Object} Instructions for creating the layout of a DOM Element
|
||||
* @returns {*} DOM Element
|
||||
*/
|
||||
Layout.prototype.layout = function (obj) {
|
||||
if (!obj.parent) {
|
||||
throw new Error('No parent element provided');
|
||||
|
@ -55,27 +80,21 @@ define(function (require) {
|
|||
throw new Error(obj.type + ' must be a string');
|
||||
}
|
||||
|
||||
// check to see if reference to DOM element is string but not class selector
|
||||
if (typeof obj.parent === 'string' && obj.parent.charAt(0) !== '.') {
|
||||
// Create a class selector
|
||||
obj.parent = '.' + obj.parent;
|
||||
}
|
||||
|
||||
// append child
|
||||
var childEl = this.appendElem(obj.parent, obj.type, obj.class);
|
||||
|
||||
if (obj.datum) {
|
||||
// Bind datum to the element
|
||||
childEl.datum(obj.datum);
|
||||
}
|
||||
|
||||
if (obj.splits) {
|
||||
// Call the split on its obj.class
|
||||
d3.select(this.el).select('.' + obj.class).call(obj.splits);
|
||||
}
|
||||
|
||||
if (obj.children) {
|
||||
// Creating the parent elem for the child nodes
|
||||
var newParent = d3.select(this.el).select('.' + obj.class)[0][0];
|
||||
|
||||
_.forEach(obj.children, function (obj) {
|
||||
|
@ -84,14 +103,21 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
|
||||
// Recursively pass children to createLayout
|
||||
this.createLayout(obj.children);
|
||||
}
|
||||
|
||||
return childEl;
|
||||
};
|
||||
|
||||
// Appends a `type` of DOM element to `el` and gives it a class name attribute `className`
|
||||
/**
|
||||
* Appends a `type` of DOM element to `el` and gives it a class name attribute `className`
|
||||
*
|
||||
* @method appendElem
|
||||
* @param el {HTMLElement} Reference to a DOM Element
|
||||
* @param type {String} DOM element type
|
||||
* @param className {String} CSS class name
|
||||
* @returns {*} Reference to D3 Selection
|
||||
*/
|
||||
Layout.prototype.appendElem = function (el, type, className) {
|
||||
if (!el || !type || !className) {
|
||||
throw new Error('Function requires that an el, type, and class be provided');
|
||||
|
@ -101,14 +127,22 @@ define(function (require) {
|
|||
// Create a DOM reference with a d3 selection
|
||||
// Need to make sure that the `el` is bound to this object
|
||||
// to prevent it from being appended to another Layout
|
||||
el = d3.select(this.el).select(el)[0][0];
|
||||
el = d3.select(this.el)
|
||||
.select(el)[0][0];
|
||||
}
|
||||
|
||||
return d3.select(el).append(type)
|
||||
return d3.select(el)
|
||||
.append(type)
|
||||
.attr('class', className);
|
||||
};
|
||||
|
||||
// Removes all DOM elements from `el`
|
||||
/**
|
||||
* Removes all DOM elements from DOM element
|
||||
*
|
||||
* @method removeAll
|
||||
* @param el {HTMLElement} Reference to DOM element
|
||||
* @returns {D3.Selection|D3.Transition.Transition} Reference to an empty DOM element
|
||||
*/
|
||||
Layout.prototype.removeAll = function (el) {
|
||||
return d3.select(el).selectAll('*').remove();
|
||||
};
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
define(function (require) {
|
||||
return function LayoutTypeFactory(Private) {
|
||||
// visLib layout types
|
||||
|
||||
/**
|
||||
* Provides the HTML layouts for each visualization class
|
||||
*
|
||||
* @module visLib
|
||||
* @submodule LayoutTypeFactory
|
||||
* @param Private {Service} Loads any function as an angular module
|
||||
* @return {Function} Returns an Object of HTML layouts for each visualization class
|
||||
*/
|
||||
return {
|
||||
histogram: Private(require('components/vislib/lib/layout/types/column_layout')),
|
||||
line: Private(require('components/vislib/lib/layout/types/column_layout')),
|
||||
|
|
|
@ -8,34 +8,32 @@ define(function () {
|
|||
return function split(selection) {
|
||||
selection.each(function (data) {
|
||||
var div = d3.select(this)
|
||||
.attr('class', function () {
|
||||
// Determine the parent class
|
||||
if (data.rows) {
|
||||
return 'chart-wrapper-row';
|
||||
} else if (data.columns) {
|
||||
return 'chart-wrapper-column';
|
||||
} else {
|
||||
return 'chart-wrapper';
|
||||
}
|
||||
});
|
||||
.attr('class', function () {
|
||||
if (data.rows) {
|
||||
return 'chart-wrapper-row';
|
||||
} else if (data.columns) {
|
||||
return 'chart-wrapper-column';
|
||||
} else {
|
||||
return 'chart-wrapper';
|
||||
}
|
||||
});
|
||||
var divClass;
|
||||
|
||||
var charts = div.selectAll('charts')
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
// Determine the child class
|
||||
if (d.rows) {
|
||||
divClass = 'chart-row';
|
||||
return d.rows;
|
||||
} else if (d.columns) {
|
||||
divClass = 'chart-column';
|
||||
return d.columns;
|
||||
} else {
|
||||
divClass = 'chart';
|
||||
return [d];
|
||||
}
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
if (d.rows) {
|
||||
divClass = 'chart-row';
|
||||
return d.rows;
|
||||
} else if (d.columns) {
|
||||
divClass = 'chart-column';
|
||||
return d.columns;
|
||||
} else {
|
||||
divClass = 'chart';
|
||||
return [d];
|
||||
}
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', function () {
|
||||
return divClass;
|
||||
|
|
|
@ -5,6 +5,7 @@ define(function () {
|
|||
* `.x-axis-chart-title` element based on the data layout.
|
||||
* For example, if the data has rows, it returns the same number of
|
||||
* `.chart-title` elements as row objects.
|
||||
* if not data.rows or data.columns, return no chart titles
|
||||
*/
|
||||
return function (selection) {
|
||||
selection.each(function (data) {
|
||||
|
@ -12,26 +13,23 @@ define(function () {
|
|||
|
||||
if (!data.series) {
|
||||
div.selectAll('.chart-title')
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.rows ? d.rows : d.columns;
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.rows ? d.rows : d.columns;
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'chart-title');
|
||||
|
||||
if (data.rows) {
|
||||
// if rows remove the x axis chart title element
|
||||
d3.select('.x-axis-chart-title').remove();
|
||||
} else {
|
||||
// if columns, remove the y axis chart title element
|
||||
d3.select('.y-axis-chart-title').remove();
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// if not data.rows or data.columns, return no chart titles
|
||||
return d3.select(this).remove();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
define(function () {
|
||||
return function XAxisSplitFactory(d3) {
|
||||
|
||||
/*
|
||||
* Adds div DOM elements to the `.x-axis-div-wrapper` element based on the data layout.
|
||||
* For example, if the data has rows, it returns the same number of
|
||||
* `.x-axis-div` elements as row objects.
|
||||
*/
|
||||
|
||||
return function (selection) {
|
||||
selection.each(function () {
|
||||
var div = d3.select(this);
|
||||
|
||||
div.selectAll('.x-axis-div')
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.columns ? d.columns : [d];
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.columns ? d.columns : [d];
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'x-axis-div');
|
||||
});
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
define(function () {
|
||||
return function YAxisSplitFactory(d3) {
|
||||
|
||||
/*
|
||||
* Adds div DOM elements to the `.y-axis-div-wrapper` element based on the data layout.
|
||||
* For example, if the data has rows, it returns the same number of
|
||||
* `.y-axis-div` elements as row objects.
|
||||
*/
|
||||
|
||||
return function (selection) {
|
||||
selection.each(function () {
|
||||
var div = d3.select(this);
|
||||
|
||||
div.selectAll('.y-axis-div')
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.rows ? d.rows : [d];
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.rows ? d.rows : [d];
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'y-axis-div');
|
||||
});
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
define(function () {
|
||||
return function ChartSplitFactory(d3) {
|
||||
|
||||
/*
|
||||
* Adds div DOM elements to the `.chart-wrapper` element based on the data layout.
|
||||
* For example, if the data has rows, it returns the same number of
|
||||
* `.chart` elements as row objects.
|
||||
*/
|
||||
|
||||
return function split(selection) {
|
||||
selection.each(function (data) {
|
||||
var div = d3.select(this)
|
||||
.attr('class', function () {
|
||||
// Determine the parent class
|
||||
if (data.rows) {
|
||||
return 'chart-wrapper-row';
|
||||
} else if (data.columns) {
|
||||
return 'chart-wrapper-column';
|
||||
} else {
|
||||
return 'chart-wrapper';
|
||||
}
|
||||
});
|
||||
.attr('class', function () {
|
||||
if (data.rows) {
|
||||
return 'chart-wrapper-row';
|
||||
} else if (data.columns) {
|
||||
return 'chart-wrapper-column';
|
||||
} else {
|
||||
return 'chart-wrapper';
|
||||
}
|
||||
});
|
||||
var divClass;
|
||||
|
||||
var charts = div.selectAll('charts')
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
// Determine the child class
|
||||
if (d.rows) {
|
||||
divClass = 'chart-row';
|
||||
return d.rows;
|
||||
} else if (d.columns) {
|
||||
divClass = 'chart-column';
|
||||
return d.columns;
|
||||
} else {
|
||||
divClass = 'chart';
|
||||
return [d];
|
||||
}
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
if (d.rows) {
|
||||
divClass = 'chart-row';
|
||||
return d.rows;
|
||||
} else if (d.columns) {
|
||||
divClass = 'chart-column';
|
||||
return d.columns;
|
||||
} else {
|
||||
divClass = 'chart';
|
||||
return [d];
|
||||
}
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', function () {
|
||||
return divClass;
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
define(function () {
|
||||
return function ChartTitleSplitFactory(d3) {
|
||||
|
||||
/*
|
||||
* Adds div DOM elements to either the `.y-axis-chart-title` element or the
|
||||
* `.x-axis-chart-title` element based on the data layout.
|
||||
* For example, if the data has rows, it returns the same number of
|
||||
* `.chart-title` elements as row objects.
|
||||
* if not data.rows or data.columns, return no chart titles
|
||||
*/
|
||||
|
||||
return function (selection) {
|
||||
selection.each(function (data) {
|
||||
var div = d3.select(this);
|
||||
|
||||
if (!data.slices) {
|
||||
div.selectAll('.chart-title')
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.rows ? d.rows : d.columns;
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.data(function (d) {
|
||||
return d.rows ? d.rows : d.columns;
|
||||
})
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'chart-title');
|
||||
|
||||
if (data.rows) {
|
||||
// if rows remove the x axis chart title element
|
||||
d3.select('.x-axis-chart-title').remove();
|
||||
} else {
|
||||
// if columns, remove the y axis chart title element
|
||||
d3.select('.y-axis-chart-title').remove();
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// if not data.rows or data.columns, return no chart titles
|
||||
return d3.select(this).remove();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
define(function (require) {
|
||||
return function ColumnLayoutFactory(d3, Private) {
|
||||
|
||||
var chartSplit = Private(require('components/vislib/lib/layout/splits/column_chart/chart_split'));
|
||||
var yAxisSplit = Private(require('components/vislib/lib/layout/splits/column_chart/y_axis_split'));
|
||||
var xAxisSplit = Private(require('components/vislib/lib/layout/splits/column_chart/x_axis_split'));
|
||||
var chartTitleSplit = Private(require('components/vislib/lib/layout/splits/column_chart/chart_title_split'));
|
||||
|
||||
/*
|
||||
/**
|
||||
* Specifies the visualization layout for column charts.
|
||||
*
|
||||
* This is done using an array of objects. The first object has
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
define(function (require) {
|
||||
return function ColumnLayoutFactory(d3, Private) {
|
||||
|
||||
var chartSplit = Private(require('components/vislib/lib/layout/splits/pie_chart/chart_split'));
|
||||
var chartTitleSplit = Private(require('components/vislib/lib/layout/splits/pie_chart/chart_title_split'));
|
||||
|
||||
/*
|
||||
/**
|
||||
* Specifies the visualization layout for column charts.
|
||||
*
|
||||
* This is done using an array of objects. The first object has
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
define(function (require) {
|
||||
return function LegendFactory(d3, Private) {
|
||||
return function LegendFactory(d3) {
|
||||
var _ = require('lodash');
|
||||
var legendHeaderTemplate = _.template(require('text!components/vislib/partials/legend_header.html'));
|
||||
|
||||
var Tooltip = Private(require('components/vislib/lib/tooltip'));
|
||||
|
||||
// Dynamically adds css file
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
/*
|
||||
* Append legend to the visualization
|
||||
* arguments:
|
||||
* el => reference to DOM element
|
||||
* labels => array of labels from the chart data
|
||||
* color => color function to assign colors to labels
|
||||
* _attr => visualization attributes
|
||||
/**
|
||||
* Appends legend to the visualization
|
||||
*
|
||||
* @class Legend
|
||||
* @constructor
|
||||
* @param vis {Object} Reference to Vis Constructor
|
||||
* @param el {HTMLElement} Reference to DOM element
|
||||
* @param labels {Array} Array of chart labels
|
||||
* @param color {Function} Color function
|
||||
* @param _attr {Object|*} Reference to Vis options
|
||||
*/
|
||||
function Legend(vis, el, labels, color, _attr) {
|
||||
if (!(this instanceof Legend)) {
|
||||
|
@ -26,7 +26,6 @@ define(function (require) {
|
|||
this.labels = labels;
|
||||
this.color = color;
|
||||
this._attr = _.defaults(_attr || {}, {
|
||||
// Legend specific attributes
|
||||
'legendClass' : 'legend-col-wrapper',
|
||||
'blurredOpacity' : 0.3,
|
||||
'focusOpacity' : 1,
|
||||
|
@ -35,58 +34,82 @@ define(function (require) {
|
|||
});
|
||||
}
|
||||
|
||||
// Add legend header
|
||||
/**
|
||||
* Adds legend header
|
||||
*
|
||||
* @method header
|
||||
* @param el {HTMLElement} Reference to DOM element
|
||||
* @param args {Object|*} Legend options
|
||||
* @returns {*} HTML element
|
||||
*/
|
||||
Legend.prototype.header = function (el, args) {
|
||||
return el.append('div')
|
||||
.attr('class', 'header')
|
||||
.append('div')
|
||||
.attr('class', 'column-labels')
|
||||
.html(function () {
|
||||
return legendHeaderTemplate(args._attr);
|
||||
});
|
||||
.attr('class', 'header')
|
||||
.append('div')
|
||||
.attr('class', 'column-labels')
|
||||
.html(function () {
|
||||
return legendHeaderTemplate(args._attr);
|
||||
});
|
||||
};
|
||||
|
||||
// Add legend list
|
||||
/**
|
||||
* Adds list to legend
|
||||
*
|
||||
* @method list
|
||||
* @param el {HTMLElement} Reference to DOM element
|
||||
* @param arrOfLabels {Array} Array of labels
|
||||
* @param args {Object|*} Legend options
|
||||
* @returns {D3.Selection} HTML element with list of labels attached
|
||||
*/
|
||||
Legend.prototype.list = function (el, arrOfLabels, args) {
|
||||
var self = this;
|
||||
|
||||
return el.append('ul')
|
||||
.attr('class', function () {
|
||||
if (args._attr.isOpen) {
|
||||
return 'legend-ul';
|
||||
}
|
||||
return 'legend-ul hidden';
|
||||
})
|
||||
.selectAll('li')
|
||||
.data(arrOfLabels)
|
||||
.enter()
|
||||
.attr('class', function () {
|
||||
if (args._attr.isOpen) {
|
||||
return 'legend-ul';
|
||||
}
|
||||
return 'legend-ul hidden';
|
||||
})
|
||||
.selectAll('li')
|
||||
.data(arrOfLabels)
|
||||
.enter()
|
||||
.append('li')
|
||||
.attr('class', function (d) {
|
||||
// class names reflect the color assigned to the labels
|
||||
return 'color ' + self.colorToClass(args.color(d));
|
||||
})
|
||||
.html(function (d) {
|
||||
// return the appropriate color for each dot
|
||||
return '<span class="dots" style="background:' + args.color(d) + '"></span>' + d;
|
||||
});
|
||||
};
|
||||
|
||||
// Create a class name based on the colors assigned to each label
|
||||
/**
|
||||
* Creates a class name based on the colors assigned to each label
|
||||
*
|
||||
* @method colorToClass
|
||||
* @param name {String} Label
|
||||
* @returns {string} CSS class name
|
||||
*/
|
||||
Legend.prototype.colorToClass = function (name) {
|
||||
return 'c' + name.replace(/[#]/g, '');
|
||||
};
|
||||
|
||||
// Render the legend
|
||||
/**
|
||||
* Renders legend
|
||||
*
|
||||
* @method render
|
||||
* @return {HTMLElement} Legend
|
||||
*/
|
||||
Legend.prototype.render = function () {
|
||||
var visEl = d3.select(this.el);
|
||||
var legendDiv = visEl.select('.' + this._attr.legendClass);
|
||||
var items = this.labels;
|
||||
var header = this.header(legendDiv, this);
|
||||
var headerIcon = visEl.select('.legend-toggle');
|
||||
var list = this.list(legendDiv, items, this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this.header(legendDiv, this);
|
||||
this.list(legendDiv, items, this);
|
||||
|
||||
// toggle
|
||||
headerIcon
|
||||
.on('click', function legendClick() {
|
||||
|
@ -109,16 +132,16 @@ define(function (require) {
|
|||
});
|
||||
|
||||
visEl.selectAll('.color')
|
||||
.on('mouseover', function (d) {
|
||||
var liClass = '.' + self.colorToClass(self.color(d));
|
||||
visEl.selectAll('.color').style('opacity', self._attr.blurredOpacity);
|
||||
|
||||
// select series on chart
|
||||
visEl.selectAll(liClass).style('opacity', self._attr.focusOpacity);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
visEl.selectAll('.color').style('opacity', self._attr.defaultOpacity);
|
||||
});
|
||||
.on('mouseover', function (d) {
|
||||
var liClass = '.' + self.colorToClass(self.color(d));
|
||||
visEl.selectAll('.color').style('opacity', self._attr.blurredOpacity);
|
||||
|
||||
// select series on chart
|
||||
visEl.selectAll(liClass).style('opacity', self._attr.focusOpacity);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
visEl.selectAll('.color').style('opacity', self._attr.defaultOpacity);
|
||||
});
|
||||
};
|
||||
|
||||
return Legend;
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
define(function (require) {
|
||||
return function TooltipFactory(d3, Private) {
|
||||
var _ = require('lodash');
|
||||
return function TooltipFactory(d3) {
|
||||
var $ = require('jquery');
|
||||
|
||||
// Dynamically adds css file
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
/*
|
||||
* Append a tooltip div element to the visualization
|
||||
* arguments:
|
||||
* el => reference to DOM element
|
||||
* formatter => tooltip formatter
|
||||
/**
|
||||
* Add tooltip and listeners to visualization elements
|
||||
*
|
||||
* @class Tooltip
|
||||
* @constructor
|
||||
* @param el {HTMLElement} Reference to DOM element
|
||||
* @param formatter {Function} Tooltip formatter
|
||||
* @param events {Constructor} Allows tooltip to return event response data
|
||||
*/
|
||||
function Tooltip(el, formatter, events) {
|
||||
if (!(this instanceof Tooltip)) {
|
||||
|
@ -23,6 +24,12 @@ define(function (require) {
|
|||
this.containerClass = 'vis-wrapper';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders tooltip
|
||||
*
|
||||
* @method render
|
||||
* @returns {Function} Renders tooltip on a D3 selection
|
||||
*/
|
||||
Tooltip.prototype.render = function () {
|
||||
var self = this;
|
||||
var tooltipFormatter = this.formatter;
|
||||
|
@ -42,26 +49,33 @@ define(function (require) {
|
|||
var element = d3.select(this);
|
||||
|
||||
element
|
||||
.on('mousemove.tip', function (d) {
|
||||
var placement = self.getTooltipPlacement(d3.event);
|
||||
var events = self.events ? self.events.eventResponse(d, i) : d;
|
||||
.on('mousemove.tip', function (d) {
|
||||
var placement = self.getTooltipPlacement(d3.event);
|
||||
var events = self.events ? self.events.eventResponse(d, i) : d;
|
||||
|
||||
// return text and position for tooltip
|
||||
return tooltipDiv.datum(events)
|
||||
.html(tooltipFormatter)
|
||||
.style('visibility', 'visible')
|
||||
.style('left', placement.left + 'px')
|
||||
.style('top', placement.top + 'px');
|
||||
})
|
||||
.on('mouseout.tip', function () {
|
||||
return tooltipDiv.style('visibility', 'hidden')
|
||||
.style('left', '-500px')
|
||||
.style('top', '-500px');
|
||||
});
|
||||
// return text and position for tooltip
|
||||
return tooltipDiv.datum(events)
|
||||
.html(tooltipFormatter)
|
||||
.style('visibility', 'visible')
|
||||
.style('left', placement.left + 'px')
|
||||
.style('top', placement.top + 'px');
|
||||
})
|
||||
.on('mouseout.tip', function () {
|
||||
return tooltipDiv.style('visibility', 'hidden')
|
||||
.style('left', '-500px')
|
||||
.style('top', '-500px');
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates values for the tooltip placement
|
||||
*
|
||||
* @method getTooltipPlacement
|
||||
* @param event {Object} D3 Events Object
|
||||
* @returns {{Object}} Coordinates for tooltip
|
||||
*/
|
||||
Tooltip.prototype.getTooltipPlacement = function (event) {
|
||||
var self = this;
|
||||
var OFFSET = 10;
|
||||
|
|
|
@ -4,16 +4,14 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
|
||||
var ErrorHandler = Private(require('components/vislib/lib/_error_handler'));
|
||||
var ChartTitle = Private(require('components/vislib/lib/chart_title'));
|
||||
|
||||
/*
|
||||
* Add an x axis to the visualization
|
||||
* aruments =>
|
||||
* el => reference to DOM element
|
||||
* xValues => array of x values from the dataset
|
||||
* ordered => data object that is defined when the data is ordered
|
||||
* xAxisFormatter => function to formatx axis tick values
|
||||
* _attr => visualization attributes
|
||||
/**
|
||||
* Adds an x axis to the visualization
|
||||
*
|
||||
* @class XAxis
|
||||
* @constructor
|
||||
* @param args {{el: (HTMLElement), xValues: (Array), ordered: (Object|*),
|
||||
* xAxisFormatter: (Function), _attr: (Object|*)}}
|
||||
*/
|
||||
function XAxis(args) {
|
||||
if (!(this instanceof XAxis)) {
|
||||
|
@ -29,16 +27,24 @@ define(function (require) {
|
|||
|
||||
_(XAxis.prototype).extend(ErrorHandler.prototype);
|
||||
|
||||
// Render the x axis
|
||||
/**
|
||||
* Renders the x axis
|
||||
*
|
||||
* @method render
|
||||
* @returns {D3.UpdateSelection} Appends x axis to visualization
|
||||
*/
|
||||
XAxis.prototype.render = function () {
|
||||
d3.select(this.el)
|
||||
.selectAll('.x-axis-div')
|
||||
.call(this.draw());
|
||||
d3.select(this.el).selectAll('.x-axis-div').call(this.draw());
|
||||
};
|
||||
|
||||
// Get the d3 scale
|
||||
// if time, return time scale
|
||||
// return d3 ordinal scale for nominal data
|
||||
/**
|
||||
* Returns d3 x axis scale function.
|
||||
* If time, return time scale, else return d3 ordinal scale for nominal data
|
||||
*
|
||||
* @method getScale
|
||||
* @param ordered {Object|*} Time information
|
||||
* @returns {*} D3 scale function
|
||||
*/
|
||||
XAxis.prototype.getScale = function (ordered) {
|
||||
if (ordered && ordered.date) {
|
||||
return d3.time.scale();
|
||||
|
@ -46,10 +52,16 @@ define(function (require) {
|
|||
return d3.scale.ordinal();
|
||||
};
|
||||
|
||||
// Add domain to the scale
|
||||
// if time, return a time domain
|
||||
// Calculate the min date, max date, and time interval;
|
||||
// return a nominal domain, i.e. array of x values
|
||||
/**
|
||||
* Add domain to the x axis scale.
|
||||
* if time, return a time domain, and calculate the min date, max date, and time interval
|
||||
* else, return a nominal (d3.scale.ordinal) domain, i.e. array of x axis values
|
||||
*
|
||||
* @method getDomain
|
||||
* @param scale {Function} D3 scale
|
||||
* @param ordered {Object} Time information
|
||||
* @returns {*} D3 scale function
|
||||
*/
|
||||
XAxis.prototype.getDomain = function (scale, ordered) {
|
||||
if (ordered && ordered.date) {
|
||||
return this.getTimeDomain(scale, ordered);
|
||||
|
@ -57,20 +69,40 @@ define(function (require) {
|
|||
return this.getOrdinalDomain(scale, this.xValues);
|
||||
};
|
||||
|
||||
// Returns a time domain
|
||||
/**
|
||||
* Returns D3 time domain
|
||||
*
|
||||
* @method getTimeDomain
|
||||
* @param scale {Function} D3 scale function
|
||||
* @param ordered {Object} Time information
|
||||
* @returns {*} D3 scale function
|
||||
*/
|
||||
XAxis.prototype.getTimeDomain = function (scale, ordered) {
|
||||
return scale.domain([ordered.min, ordered.max]);
|
||||
};
|
||||
|
||||
// Return a nominal(d3 ordinal) domain
|
||||
/**
|
||||
* Return a nominal(d3 ordinal) domain
|
||||
*
|
||||
* @method getOrdinalDomain
|
||||
* @param scale {Function} D3 scale function
|
||||
* @param xValues {Array} Array of x axis values
|
||||
* @returns {*} D3 scale function
|
||||
*/
|
||||
XAxis.prototype.getOrdinalDomain = function (scale, xValues) {
|
||||
|
||||
return scale.domain(xValues);
|
||||
};
|
||||
|
||||
// Return the range for the x axis scale
|
||||
// if time, return a normal range
|
||||
// if nominal, return rangeBands with a default (0.1) spacer specified
|
||||
/**
|
||||
* Return the range for the x axis scale
|
||||
* if time, return a normal range, else if nominal, return rangeBands with a default (0.1) spacer specified
|
||||
*
|
||||
* @method getRange
|
||||
* @param scale {Function} D3 scale function
|
||||
* @param ordered {Object} Time information
|
||||
* @param width {Number} HTML Element width
|
||||
* @returns {*} D3 scale function
|
||||
*/
|
||||
XAxis.prototype.getRange = function (scale, ordered, width) {
|
||||
if (ordered && ordered.date) {
|
||||
return scale.range([0, width]);
|
||||
|
@ -78,18 +110,26 @@ define(function (require) {
|
|||
return scale.rangeBands([0, width], 0.1);
|
||||
};
|
||||
|
||||
// Return the x axis scale
|
||||
/**
|
||||
* Return the x axis scale
|
||||
*
|
||||
* @method getXScale
|
||||
* @param ordered {Object} Time information
|
||||
* @param width {Number} HTML Element width
|
||||
* @returns {*} D3 x scale function
|
||||
*/
|
||||
XAxis.prototype.getXScale = function (ordered, width) {
|
||||
var scale = this.getScale(ordered);
|
||||
var domain = this.getDomain(scale, ordered);
|
||||
var xScale = this.getRange(domain, ordered, width);
|
||||
|
||||
return xScale;
|
||||
return this.getRange(domain, ordered, width);
|
||||
};
|
||||
|
||||
// Create the d3 xAxis function
|
||||
// save a reference to the xScale
|
||||
// Scale should never === `NaN`
|
||||
/**
|
||||
* Creates d3 xAxis function
|
||||
*
|
||||
* @method getXAxis
|
||||
* @param width {Number} HTML Element width
|
||||
*/
|
||||
XAxis.prototype.getXAxis = function (width) {
|
||||
this.xScale = this.getXScale(this.ordered, width);
|
||||
|
||||
|
@ -104,10 +144,14 @@ define(function (require) {
|
|||
.orient('bottom');
|
||||
};
|
||||
|
||||
// Returns a function that renders the x axis
|
||||
/**
|
||||
* Renders the x axis
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Renders the x axis to a D3 selection
|
||||
*/
|
||||
XAxis.prototype.draw = function () {
|
||||
var self = this;
|
||||
var margin = this._attr.margin;
|
||||
var div;
|
||||
var width;
|
||||
var height;
|
||||
|
@ -124,9 +168,6 @@ define(function (require) {
|
|||
|
||||
selection.each(function () {
|
||||
|
||||
// Validate that width and height are not 0 or `NaN`
|
||||
// return access to xAxis variable on the object
|
||||
// append svg and x axis
|
||||
div = d3.select(this);
|
||||
width = parentWidth / n;
|
||||
height = $(this).height();
|
||||
|
@ -149,9 +190,14 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Returns a function that evaluates scale type and applies
|
||||
// filters tick labels on time scales
|
||||
// rotates and truncates labels on nominal/ordinal scales
|
||||
/**
|
||||
* Returns a function that evaluates scale type and applies
|
||||
* filters tick labels on time scales
|
||||
* rotates and truncates labels on nominal/ordinal scales
|
||||
*
|
||||
* @method filterOrRotate
|
||||
* @returns {Function} Filters or rotates x axis tick labels
|
||||
*/
|
||||
XAxis.prototype.filterOrRotate = function () {
|
||||
var self = this;
|
||||
var ordered = self.ordered;
|
||||
|
@ -163,7 +209,7 @@ define(function (require) {
|
|||
axis = d3.select(this);
|
||||
labels = axis.selectAll('.tick text');
|
||||
|
||||
if (!self.ordered) {
|
||||
if (!ordered) {
|
||||
axis.call(self.rotateAxisLabels());
|
||||
} else {
|
||||
axis.call(self.filterAxisLabels());
|
||||
|
@ -177,7 +223,11 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Rotate the axis tick labels within selection
|
||||
/**
|
||||
* Rotate the axis tick labels within selection
|
||||
*
|
||||
* @returns {Function} Rotates x axis tick labels of a D3 selection
|
||||
*/
|
||||
XAxis.prototype.rotateAxisLabels = function () {
|
||||
var self = this;
|
||||
var text;
|
||||
|
@ -223,7 +273,14 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Returns a string that is truncated to fit size
|
||||
/**
|
||||
* Returns a string that is truncated to fit size
|
||||
*
|
||||
* @method truncateLabel
|
||||
* @param text {HTMLElement}
|
||||
* @param size {Number}
|
||||
* @returns {*|jQuery}
|
||||
*/
|
||||
XAxis.prototype.truncateLabel = function (text, size) {
|
||||
var node = d3.select(text).node();
|
||||
var str = $(node).text();
|
||||
|
@ -243,11 +300,16 @@ define(function (require) {
|
|||
return str;
|
||||
};
|
||||
|
||||
// Filter out text labels by width and position on axis
|
||||
// trims labels that would overlap each other
|
||||
// or extend past left or right edges
|
||||
// if prev label pos (or 0) + half of label width is < label pos
|
||||
// and label pos + half width is not > width of axis
|
||||
/**
|
||||
* Filter out text labels by width and position on axis
|
||||
* trims labels that would overlap each other
|
||||
* or extend past left or right edges
|
||||
* if prev label pos (or 0) + half of label width is < label pos
|
||||
* and label pos + half width is not > width of axis
|
||||
*
|
||||
* @method filterAxisLabels
|
||||
* @returns {Function}
|
||||
*/
|
||||
XAxis.prototype.filterAxisLabels = function () {
|
||||
var self = this;
|
||||
var startX = 0;
|
||||
|
@ -260,7 +322,7 @@ define(function (require) {
|
|||
|
||||
return function (selection) {
|
||||
selection.selectAll('.tick text')
|
||||
.text(function (d, i) {
|
||||
.text(function (d) {
|
||||
par = d3.select(this.parentNode).node();
|
||||
myX = self.xScale(d);
|
||||
myWidth = par.getBBox().width * padding;
|
||||
|
@ -277,21 +339,20 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Returns a function that adjusts axis titles and
|
||||
// chart title transforms to fit axis label divs.
|
||||
// Sets transform of x-axis-title to fit .x-axis-title div width
|
||||
// if x-axis-chart-titles, set transform of x-axis-chart-titles
|
||||
// to fit .chart-title div width
|
||||
/**
|
||||
* Returns a function that adjusts axis titles and
|
||||
* chart title transforms to fit axis label divs.
|
||||
* Sets transform of x-axis-title to fit .x-axis-title div width
|
||||
* if x-axis-chart-titles, set transform of x-axis-chart-titles
|
||||
* to fit .chart-title div width
|
||||
*
|
||||
* @method fitTitles
|
||||
* @returns {Function}
|
||||
*/
|
||||
XAxis.prototype.fitTitles = function () {
|
||||
var self = this;
|
||||
var visEls = $('.vis-wrapper');
|
||||
var visEl;
|
||||
var xAxisTitle;
|
||||
var yAxisTitle;
|
||||
var xAxisChartTitle;
|
||||
var yAxisChartTitle;
|
||||
var titleWidth;
|
||||
var titleHeight;
|
||||
var text;
|
||||
var titles;
|
||||
|
||||
|
@ -306,16 +367,16 @@ define(function (require) {
|
|||
var titleHeight = yAxisTitle.height();
|
||||
|
||||
text = visEl.select('.x-axis-title')
|
||||
.select('svg')
|
||||
.attr('width', titleWidth)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(' + (titleWidth / 2) + ',11)');
|
||||
.select('svg')
|
||||
.attr('width', titleWidth)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(' + (titleWidth / 2) + ',11)');
|
||||
|
||||
text = visEl.select('.y-axis-title')
|
||||
.select('svg')
|
||||
.attr('height', titleHeight)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)');
|
||||
.select('svg')
|
||||
.attr('height', titleHeight)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)');
|
||||
|
||||
if ($visEl.find('.x-axis-chart-title').length) {
|
||||
xAxisChartTitle = $visEl.find('.x-axis-chart-title');
|
||||
|
@ -324,10 +385,10 @@ define(function (require) {
|
|||
titles = visEl.select('.x-axis-chart-title').selectAll('.chart-title');
|
||||
titles.each(function () {
|
||||
text = d3.select(this)
|
||||
.select('svg')
|
||||
.attr('width', titleWidth)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(' + (titleWidth / 2) + ',11)');
|
||||
.select('svg')
|
||||
.attr('width', titleWidth)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(' + (titleWidth / 2) + ',11)');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -338,10 +399,10 @@ define(function (require) {
|
|||
titles = visEl.select('.y-axis-chart-title').selectAll('.chart-title');
|
||||
titles.each(function () {
|
||||
text = d3.select(this)
|
||||
.select('svg')
|
||||
.attr('height', titleHeight)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)');
|
||||
.select('svg')
|
||||
.attr('height', titleHeight)
|
||||
.select('text')
|
||||
.attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -350,12 +411,15 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
// Appends div to make .y-axis-spacer-block
|
||||
// match height of .x-axis-wrapper
|
||||
/**
|
||||
* Appends div to make .y-axis-spacer-block
|
||||
* match height of .x-axis-wrapper
|
||||
*
|
||||
* @method updateXaxisHeight
|
||||
*/
|
||||
XAxis.prototype.updateXaxisHeight = function () {
|
||||
var self = this;
|
||||
var selection = d3.selectAll('.vis-wrapper');
|
||||
var $selection = $('.vis-wrapper');
|
||||
var titleHts = 30;
|
||||
var xAxisLabelHt = 15;
|
||||
|
||||
|
|
|
@ -5,11 +5,12 @@ define(function (require) {
|
|||
|
||||
var ErrorHandler = Private(require('components/vislib/lib/_error_handler'));
|
||||
|
||||
/*
|
||||
* Append a y axis to the visualization
|
||||
* arguments:
|
||||
* el => reference to DOM element
|
||||
* _attr => visualization attributes
|
||||
/**
|
||||
* Appends y axis to the visualization
|
||||
*
|
||||
* @class YAxis
|
||||
* @constructor
|
||||
* @param args {{el: (HTMLElement), yMax: (Number), _attr: (Object|*)}}
|
||||
*/
|
||||
function YAxis(args) {
|
||||
this.el = args.el;
|
||||
|
@ -19,23 +20,40 @@ define(function (require) {
|
|||
|
||||
_(YAxis.prototype).extend(ErrorHandler.prototype);
|
||||
|
||||
// Render the y axis
|
||||
/**
|
||||
* Renders the y axis
|
||||
*
|
||||
* @method render
|
||||
* @return {D3.UpdateSelection} Renders y axis to visualization
|
||||
*/
|
||||
YAxis.prototype.render = function () {
|
||||
d3.select(this.el).selectAll('.y-axis-div').call(this.draw());
|
||||
};
|
||||
|
||||
// Return the d3 y scale
|
||||
/**
|
||||
* Creates the d3 y scale function
|
||||
*
|
||||
* @method getYScale
|
||||
* @param height {Number} DOM Element height
|
||||
* @returns {D3.Scale.QuantitiveScale|*} D3 yScale function
|
||||
*/
|
||||
YAxis.prototype.getYScale = function (height) {
|
||||
// save reference to y scale
|
||||
this.yScale = d3.scale.linear()
|
||||
.domain([0, this.yMax])
|
||||
.range([height, 0])
|
||||
.nice(this.tickScale(height));
|
||||
.domain([0, this.yMax])
|
||||
.range([height, 0])
|
||||
.nice(this.tickScale(height));
|
||||
|
||||
return this.yScale;
|
||||
};
|
||||
|
||||
// Return the d3 y axis
|
||||
/**
|
||||
* Creates the d3 y axis function
|
||||
*
|
||||
* @method getYAxis
|
||||
* @param height {Number} DOM Element height
|
||||
* @returns {D3.Svg.Axis|*} D3 yAxis function
|
||||
*/
|
||||
YAxis.prototype.getYAxis = function (height) {
|
||||
var yScale = this.getYScale(height);
|
||||
|
||||
|
@ -46,10 +64,10 @@ define(function (require) {
|
|||
|
||||
// Create the d3 yAxis function
|
||||
this.yAxis = d3.svg.axis()
|
||||
.scale(yScale)
|
||||
.tickFormat(d3.format('s'))
|
||||
.ticks(this.tickScale(height))
|
||||
.orient('left');
|
||||
.scale(yScale)
|
||||
.tickFormat(d3.format('s'))
|
||||
.ticks(this.tickScale(height))
|
||||
.orient('left');
|
||||
|
||||
if (this.yScale.domain()[1] <= 10) {
|
||||
this.yAxis.tickFormat(d3.format('n'));
|
||||
|
@ -58,20 +76,31 @@ define(function (require) {
|
|||
return this.yAxis;
|
||||
};
|
||||
|
||||
// Create a tick scale for the y axis that modifies the number of ticks
|
||||
// based on the height of the wrapping DOM element
|
||||
/**
|
||||
* Create a tick scale for the y axis that modifies the number of ticks
|
||||
* based on the height of the wrapping DOM element
|
||||
* Avoid using even numbers in the yTickScale.range
|
||||
* Causes the top most tickValue in the chart to be missing
|
||||
*
|
||||
* @method tickScale
|
||||
* @param height {Number} DOM element height
|
||||
* @returns {number} Number of y axis ticks
|
||||
*/
|
||||
YAxis.prototype.tickScale = function (height) {
|
||||
// Avoid using even numbers in the yTickScale.range
|
||||
// Causes the top most tickValue in the chart to be missing
|
||||
var yTickScale = d3.scale.linear()
|
||||
.clamp(true)
|
||||
.domain([20, 40, 1000])
|
||||
.range([0, 3, 11]);
|
||||
.clamp(true)
|
||||
.domain([20, 40, 1000])
|
||||
.range([0, 3, 11]);
|
||||
|
||||
return Math.ceil(yTickScale(height));
|
||||
};
|
||||
|
||||
// Return a function that renders the y axis
|
||||
/**
|
||||
* Renders the y axis to the visualization
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Renders y axis to visualization
|
||||
*/
|
||||
YAxis.prototype.draw = function () {
|
||||
var self = this;
|
||||
var margin = this._attr.margin;
|
||||
|
@ -96,13 +125,13 @@ define(function (require) {
|
|||
|
||||
// Append svg and y axis
|
||||
svg = div.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height + margin.top + margin.bottom);
|
||||
.attr('width', width)
|
||||
.attr('height', height + margin.top + margin.bottom);
|
||||
|
||||
svg.append('g')
|
||||
.attr('class', 'y axis')
|
||||
.attr('transform', 'translate(' + (width - 2) + ',' + margin.top + ')')
|
||||
.call(yAxis);
|
||||
.attr('class', 'y axis')
|
||||
.attr('transform', 'translate(' + (width - 2) + ',' + margin.top + ')')
|
||||
.call(yAxis);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
define(function (require) {
|
||||
return function VisFactory(d3, Private) {
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
var ResizeChecker = Private(require('components/vislib/lib/resize_checker'));
|
||||
|
@ -10,12 +9,13 @@ define(function (require) {
|
|||
var errors = require('errors');
|
||||
require('css!components/vislib/styles/main.css');
|
||||
|
||||
/*
|
||||
* Visualization controller. Exposed API for creating visualizations.
|
||||
* arguments:
|
||||
* $el => jquery reference to a DOM element
|
||||
* config => object of params for the chart.
|
||||
* e.g. type: 'column', addLegend: true, ...
|
||||
/**
|
||||
* Creates the visualizations.
|
||||
*
|
||||
* @class Vis
|
||||
* @constructor
|
||||
* @param $el {HTMLElement} jQuery selected HTML element
|
||||
* @param config {Object} Parameters that define the chart type and chart options
|
||||
*/
|
||||
_(Vis).inherits(Events);
|
||||
function Vis($el, config) {
|
||||
|
@ -35,7 +35,12 @@ define(function (require) {
|
|||
this.resizeChecker.on('resize', this.resize);
|
||||
}
|
||||
|
||||
// Exposed API for rendering charts.
|
||||
/**
|
||||
* Renders the visualization
|
||||
*
|
||||
* @method render
|
||||
* @param data {Object} Elasticsearch query results
|
||||
*/
|
||||
Vis.prototype.render = function (data) {
|
||||
var chartType = this._attr.type;
|
||||
|
||||
|
@ -43,25 +48,28 @@ define(function (require) {
|
|||
throw new Error('No valid data!');
|
||||
}
|
||||
|
||||
// Save data to this object and new up the Handler constructor
|
||||
this.data = data;
|
||||
this.handler = handlerTypes[chartType](this) || handlerTypes.column(this);
|
||||
|
||||
try {
|
||||
this.handler.render();
|
||||
} catch (error) {
|
||||
// if involving height and width of the container, log error to screen
|
||||
// If involving height and width of the container, log error to screen.
|
||||
// Because we have to wait for the DOM element to initialize, we do not
|
||||
// want to throw an error when the DOM `el` is zero
|
||||
if (error instanceof errors.ContainerTooSmall) {
|
||||
this.handler.error(error.message);
|
||||
} else {
|
||||
console.error(error.message);
|
||||
console.error(error.stack);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Resize the chart
|
||||
/**
|
||||
* Resizes the visualization
|
||||
*
|
||||
* @method resize
|
||||
*/
|
||||
Vis.prototype.resize = function () {
|
||||
if (!this.data) {
|
||||
// TODO: need to come up with a solution for resizing when no data is available
|
||||
|
@ -70,25 +78,38 @@ define(function (require) {
|
|||
this.render(this.data);
|
||||
};
|
||||
|
||||
// Destroy the chart
|
||||
/**
|
||||
* Destroys the visualization
|
||||
* Removes chart and all elements associated with it.
|
||||
* Remove event listeners and pass destroy call down to owned objects.
|
||||
*
|
||||
* @method destroy
|
||||
*/
|
||||
Vis.prototype.destroy = function () {
|
||||
// Removing chart and all elements associated with it
|
||||
d3.select(this.el).selectAll('*').remove();
|
||||
|
||||
// remove event listeners
|
||||
this.resizeChecker.off('resize', this.resize);
|
||||
|
||||
// pass destroy call down to owned objects
|
||||
this.resizeChecker.destroy();
|
||||
};
|
||||
|
||||
// Set attributes on the chart
|
||||
/**
|
||||
* Sets attributes on the visualization
|
||||
*
|
||||
* @method set
|
||||
* @param name {String} An attribute name
|
||||
* @param val {*} Value to which the attribute name is set
|
||||
*/
|
||||
Vis.prototype.set = function (name, val) {
|
||||
this._attr[name] = val;
|
||||
this.render(this.data);
|
||||
};
|
||||
|
||||
// Get attributes from the chart
|
||||
/**
|
||||
* Gets attributes from the visualization
|
||||
*
|
||||
* @method get
|
||||
* @param name {String} An attribute name
|
||||
* @returns {*} The value of the attribute name
|
||||
*/
|
||||
Vis.prototype.get = function (name) {
|
||||
return this._attr[name];
|
||||
};
|
||||
|
|
|
@ -6,9 +6,14 @@ define(function (require) {
|
|||
var Dispatch = Private(require('components/vislib/lib/dispatch'));
|
||||
var Tooltip = Private(require('components/vislib/lib/tooltip'));
|
||||
|
||||
/*
|
||||
* Base Class for all visualizations.
|
||||
* Exposes a render method.
|
||||
/**
|
||||
* The Base Class for all visualizations.
|
||||
*
|
||||
* @class Chart
|
||||
* @constructor
|
||||
* @param handler {Object} Reference to the Handler Class Constructor
|
||||
* @param el {HTMLElement} HTML element to which the chart will be appended
|
||||
* @param chartData {Object} Elasticsearch query results for this specific chart
|
||||
*/
|
||||
function Chart(handler, el, chartData) {
|
||||
if (!(this instanceof Chart)) {
|
||||
|
@ -18,23 +23,36 @@ define(function (require) {
|
|||
this.handler = handler;
|
||||
this.chartEl = el;
|
||||
this.chartData = chartData;
|
||||
|
||||
var events = this.events = new Dispatch(handler, chartData);
|
||||
|
||||
if (handler._attr.addTooltip) {
|
||||
var $el = this.handler.el;
|
||||
var formatter = this.handler.data.get('tooltipFormatter');
|
||||
// Add tooltip
|
||||
|
||||
this.tooltip = new Tooltip($el, formatter, events);
|
||||
}
|
||||
|
||||
this._attr = _.defaults(handler._attr || {}, {});
|
||||
}
|
||||
|
||||
// Render the visualization.
|
||||
/**
|
||||
* Renders the chart(s)
|
||||
*
|
||||
* @method render
|
||||
* @returns {HTMLElement} Contains the D3 chart
|
||||
*/
|
||||
Chart.prototype.render = function () {
|
||||
return d3.select(this.chartEl).call(this.draw());
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a CSS class name
|
||||
*
|
||||
* @method colorToClass
|
||||
* @param label {String} Data point label
|
||||
* @returns {String} CSS class name
|
||||
*/
|
||||
Chart.prototype.colorToClass = function (label) {
|
||||
return 'color ' + Legend.prototype.colorToClass.call(null, label);
|
||||
};
|
||||
|
|
|
@ -5,12 +5,17 @@ define(function (require) {
|
|||
|
||||
var Chart = Private(require('components/vislib/visualizations/_chart'));
|
||||
var errors = require('errors');
|
||||
|
||||
// Dynamically adds css file
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
/*
|
||||
* Column chart visualization => vertical bars, stacked bars
|
||||
/**
|
||||
* Vertical Bar Chart Visualization: renders vertical and/or stacked bars
|
||||
*
|
||||
* @class ColumnChart
|
||||
* @constructor
|
||||
* @extends Chart
|
||||
* @param handler {Object} Reference to the Handler Class Constructor
|
||||
* @param el {HTMLElement} HTML element to which the chart will be appended
|
||||
* @param chartData {Object} Elasticsearch query results for this specific chart
|
||||
*/
|
||||
_(ColumnChart).inherits(Chart);
|
||||
function ColumnChart(handler, chartEl, chartData) {
|
||||
|
@ -18,6 +23,7 @@ define(function (require) {
|
|||
return new ColumnChart(handler, chartEl, chartData);
|
||||
}
|
||||
|
||||
// TODO: refactor
|
||||
var raw;
|
||||
var fieldIndex;
|
||||
|
||||
|
@ -26,17 +32,24 @@ define(function (require) {
|
|||
fieldIndex = _.findIndex(raw, {'categoryName': 'group'});
|
||||
}
|
||||
|
||||
this.fieldFormatter = raw && raw[fieldIndex] ? raw[fieldIndex].field.format.convert : function (d) { return d; };
|
||||
this.fieldFormatter = (raw && raw[fieldIndex]) ? raw[fieldIndex].field.format.convert : function (d) { return d; };
|
||||
|
||||
ColumnChart.Super.apply(this, arguments);
|
||||
|
||||
// Column chart specific attributes
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
xValue: function (d, i) { return d.x; },
|
||||
yValue: function (d, i) { return d.y; }
|
||||
xValue: function (d) { return d.x; },
|
||||
yValue: function (d) { return d.y; }
|
||||
});
|
||||
}
|
||||
|
||||
// Stack data
|
||||
/**
|
||||
* Stacks chart data values
|
||||
*
|
||||
* @method stackData
|
||||
* @param data {Object} Elasticsearch query result for this chart
|
||||
* @returns {Array} Stacked data objects with x, y, and y0 values
|
||||
*/
|
||||
// TODO: refactor so that this is called from the data module
|
||||
ColumnChart.prototype.stackData = function (data) {
|
||||
var self = this;
|
||||
|
@ -53,6 +66,14 @@ define(function (require) {
|
|||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds SVG rect to Vertical Bar Chart
|
||||
*
|
||||
* @method addBars
|
||||
* @param svg {HTMLElement} SVG to which rect are appended
|
||||
* @param layers {Array} Chart data array
|
||||
* @returns {D3.UpdateSelection} SVG with rect added
|
||||
*/
|
||||
ColumnChart.prototype.addBars = function (svg, layers) {
|
||||
var self = this;
|
||||
var data = this.chartData;
|
||||
|
@ -64,7 +85,6 @@ define(function (require) {
|
|||
var layer;
|
||||
var bars;
|
||||
|
||||
// Data layers
|
||||
layer = svg.selectAll('.layer')
|
||||
.data(layers)
|
||||
.enter().append('g')
|
||||
|
@ -72,18 +92,18 @@ define(function (require) {
|
|||
return i;
|
||||
});
|
||||
|
||||
// Append the bars
|
||||
bars = layer.selectAll('rect')
|
||||
.data(function (d) {
|
||||
return d;
|
||||
});
|
||||
|
||||
// exit
|
||||
bars.exit().remove();
|
||||
bars
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
bars.enter()
|
||||
.append('rect')
|
||||
bars
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('class', function (d) {
|
||||
return self.colorToClass(color(self.fieldFormatter(d.label)));
|
||||
})
|
||||
|
@ -91,32 +111,29 @@ define(function (require) {
|
|||
return color(self.fieldFormatter(d.label));
|
||||
});
|
||||
|
||||
// update
|
||||
bars
|
||||
.attr('x', function (d) {
|
||||
return xScale(d.x);
|
||||
})
|
||||
.attr('width', function () {
|
||||
var barWidth;
|
||||
var barSpacing;
|
||||
.attr('x', function (d) {
|
||||
return xScale(d.x);
|
||||
})
|
||||
.attr('width', function () {
|
||||
var barWidth;
|
||||
var barSpacing;
|
||||
|
||||
if (data.ordered && data.ordered.date) {
|
||||
barWidth = xScale(data.ordered.min + data.ordered.interval) - xScale(data.ordered.min);
|
||||
barSpacing = barWidth * 0.25;
|
||||
if (data.ordered && data.ordered.date) {
|
||||
barWidth = xScale(data.ordered.min + data.ordered.interval) - xScale(data.ordered.min);
|
||||
barSpacing = barWidth * 0.25;
|
||||
|
||||
return barWidth - barSpacing;
|
||||
}
|
||||
return barWidth - barSpacing;
|
||||
}
|
||||
return xScale.rangeBand();
|
||||
})
|
||||
.attr('y', function (d) {
|
||||
return yScale(d.y0 + d.y);
|
||||
})
|
||||
.attr('height', function (d) {
|
||||
return yScale(d.y0) - yScale(d.y0 + d.y);
|
||||
});
|
||||
|
||||
return xScale.rangeBand();
|
||||
})
|
||||
.attr('y', function (d) {
|
||||
return yScale(d.y0 + d.y);
|
||||
})
|
||||
.attr('height', function (d) {
|
||||
return yScale(d.y0) - yScale(d.y0 + d.y);
|
||||
});
|
||||
|
||||
// Add tooltip
|
||||
if (isTooltip) {
|
||||
bars.call(tooltip.render());
|
||||
}
|
||||
|
@ -124,20 +141,25 @@ define(function (require) {
|
|||
return bars;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds Events to SVG rect
|
||||
*
|
||||
* @method addBarEvents
|
||||
* @param svg {HTMLElement} chart SVG
|
||||
* @param bars {D3.UpdateSelection} SVG rect
|
||||
* @param brush {Function} D3 brush function
|
||||
* @returns {HTMLElement} rect with event listeners attached
|
||||
*/
|
||||
ColumnChart.prototype.addBarEvents = function (svg, bars, brush) {
|
||||
var self = this;
|
||||
var events = this.events;
|
||||
var dispatch = this.events._attr.dispatch;
|
||||
var addBrush = this._attr.addBrushing;
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var startXInv;
|
||||
|
||||
bars
|
||||
.on('mouseover.bar', function (d, i) {
|
||||
d3.select(this)
|
||||
.classed('hover', true)
|
||||
.style('stroke', '#333')
|
||||
.style('cursor', 'pointer');
|
||||
|
||||
self.mouseOverBar(this);
|
||||
dispatch.hover(events.eventResponse(d, i));
|
||||
d3.event.stopPropagation();
|
||||
})
|
||||
|
@ -145,7 +167,7 @@ define(function (require) {
|
|||
if (addBrush) {
|
||||
var bar = d3.select(this);
|
||||
var startX = d3.mouse(svg.node());
|
||||
startXInv = xScale.invert(startX[0]);
|
||||
var startXInv = xScale.invert(startX[0]);
|
||||
|
||||
// Reset the brush value
|
||||
brush.extent([startXInv, startXInv]);
|
||||
|
@ -163,13 +185,44 @@ define(function (require) {
|
|||
d3.event.stopPropagation();
|
||||
})
|
||||
.on('mouseout.bar', function () {
|
||||
d3.select(this).classed('hover', false)
|
||||
.style('stroke', null);
|
||||
self.mouseOutBar(this);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouseover Behavior
|
||||
*
|
||||
* @method mouseOverBar
|
||||
* @param that {Object} Reference to this object
|
||||
* @returns {D3.Selection} this object with '.hover' class true
|
||||
*/
|
||||
ColumnChart.prototype.mouseOverBar = function (that) {
|
||||
return d3.select(that)
|
||||
.classed('hover', true)
|
||||
.style('stroke', '#333')
|
||||
.style('cursor', 'pointer');
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouseout Behavior
|
||||
*
|
||||
* @method mouseOutBar
|
||||
* @param that {Object} Reference to this object
|
||||
* @returns {D3.Selection} this object with '.hover' class false
|
||||
*/
|
||||
ColumnChart.prototype.mouseOutBar = function (that) {
|
||||
return d3.select(that)
|
||||
.classed('hover', false)
|
||||
.style('stroke', null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders d3 visualization
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Creates the vertical bar chart
|
||||
*/
|
||||
ColumnChart.prototype.draw = function () {
|
||||
// Attributes
|
||||
var self = this;
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var $elem = $(this.chartEl);
|
||||
|
@ -189,10 +242,8 @@ define(function (require) {
|
|||
|
||||
return function (selection) {
|
||||
selection.each(function (data) {
|
||||
// Stack data
|
||||
layers = self.stackData(data);
|
||||
|
||||
// Get the width and height
|
||||
width = elWidth;
|
||||
height = elHeight - margin.top - margin.bottom;
|
||||
|
||||
|
@ -200,28 +251,21 @@ define(function (require) {
|
|||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
|
||||
// Select the current DOM element
|
||||
div = d3.select(this);
|
||||
|
||||
// Create the canvas for the visualization
|
||||
svg = div.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', 'translate(0,' + margin.top + ')');
|
||||
|
||||
// addBrush canvas and return brush function
|
||||
brush = self.events.addBrush(xScale, svg);
|
||||
|
||||
// add bars
|
||||
bars = self.addBars(svg, layers);
|
||||
|
||||
// add events to bars
|
||||
if (isEvents) {
|
||||
self.addBarEvents(svg, bars, brush);
|
||||
}
|
||||
|
||||
// chart base line
|
||||
var line = svg.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', height)
|
||||
|
|
|
@ -4,16 +4,25 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
|
||||
var Chart = Private(require('components/vislib/visualizations/_chart'));
|
||||
|
||||
// Dynamically adds css file
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
/**
|
||||
* Line Chart Visualization
|
||||
*
|
||||
* @class LineChart
|
||||
* @constructor
|
||||
* @extends Chart
|
||||
* @param handler {Object} Reference to the Handler Class Constructor
|
||||
* @param el {HTMLElement} HTML element to which the chart will be appended
|
||||
* @param chartData {Object} Elasticsearch query results for this specific chart
|
||||
*/
|
||||
_(LineChart).inherits(Chart);
|
||||
function LineChart(handler, chartEl, chartData) {
|
||||
if (!(this instanceof LineChart)) {
|
||||
return new LineChart(handler, chartEl, chartData);
|
||||
}
|
||||
|
||||
// TODO: refactor
|
||||
var raw;
|
||||
var fieldIndex;
|
||||
|
||||
|
@ -22,7 +31,7 @@ define(function (require) {
|
|||
fieldIndex = _.findIndex(raw, {'categoryName': 'group'});
|
||||
}
|
||||
|
||||
this.fieldFormatter = raw && raw[fieldIndex] ? raw[fieldIndex].field.format.convert : function (d) { return d; };
|
||||
this.fieldFormatter = (raw && raw[fieldIndex]) ? raw[fieldIndex].field.format.convert : function (d) { return d; };
|
||||
|
||||
LineChart.Super.apply(this, arguments);
|
||||
// Line chart specific attributes
|
||||
|
@ -33,6 +42,13 @@ define(function (require) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Events to SVG circle
|
||||
*
|
||||
* @method addCircleEvents
|
||||
* @param circles {D3.UpdateSelection} Reference to SVG circle
|
||||
* @returns {D3.UpdateSelection} SVG circles with event listeners attached
|
||||
*/
|
||||
LineChart.prototype.addCircleEvents = function (circles) {
|
||||
var events = this.events;
|
||||
var dispatch = this.events._attr.dispatch;
|
||||
|
@ -62,6 +78,14 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds circles to SVG
|
||||
*
|
||||
* @method addCircles
|
||||
* @param svg {HTMLElement} SVG to which rect are appended
|
||||
* @param data {Array} Array of object data points
|
||||
* @returns {D3.UpdateSelection} SVG with circles added
|
||||
*/
|
||||
LineChart.prototype.addCircles = function (svg, data) {
|
||||
var self = this;
|
||||
var color = this.handler.data.getColorFunc();
|
||||
|
@ -81,19 +105,16 @@ define(function (require) {
|
|||
.append('g')
|
||||
.attr('class', 'points');
|
||||
|
||||
// Append the bars
|
||||
circles = layer
|
||||
.selectAll('rect')
|
||||
.data(function appendData(d) {
|
||||
return d;
|
||||
});
|
||||
|
||||
// exit
|
||||
circles
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
circles
|
||||
.enter()
|
||||
.append('circle')
|
||||
|
@ -108,7 +129,6 @@ define(function (require) {
|
|||
})
|
||||
.attr('stroke-width', circleStrokeWidth);
|
||||
|
||||
// update
|
||||
circles
|
||||
.attr('cx', function cx(d) {
|
||||
if (ordered && ordered.date) {
|
||||
|
@ -121,7 +141,6 @@ define(function (require) {
|
|||
})
|
||||
.attr('r', circleRadius);
|
||||
|
||||
// Add tooltip
|
||||
if (isTooltip) {
|
||||
circles.call(tooltip.render());
|
||||
}
|
||||
|
@ -129,6 +148,14 @@ define(function (require) {
|
|||
return circles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds path to SVG
|
||||
*
|
||||
* @method addLines
|
||||
* @param svg {HTMLElement} SVG to which path are appended
|
||||
* @param data {Array} Array of object data points
|
||||
* @returns {D3.UpdateSelection} SVG with paths added
|
||||
*/
|
||||
LineChart.prototype.addLines = function (svg, data) {
|
||||
var self = this;
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
|
@ -173,29 +200,41 @@ define(function (require) {
|
|||
return lines;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds SVG clipPath
|
||||
*
|
||||
* @method addClipPath
|
||||
* @param svg {HTMLElement} SVG to which clipPath is appended
|
||||
* @param width {Number} SVG width
|
||||
* @param height {Number} SVG height
|
||||
* @returns {D3.UpdateSelection} SVG with clipPath added
|
||||
*/
|
||||
LineChart.prototype.addClipPath = function (svg, width, height) {
|
||||
// Prevents circles from being clipped at the top of the chart
|
||||
var clipPathBuffer = 5;
|
||||
var startX = 0;
|
||||
var startY = 0 - clipPathBuffer;
|
||||
var id = 'chart-area' + _.uniqueId();
|
||||
|
||||
// Creating clipPath
|
||||
return svg
|
||||
.attr('clip-path', 'url(#' + id + ')')
|
||||
.append('clipPath')
|
||||
.attr('id', id)
|
||||
.attr('id', id)
|
||||
.append('rect')
|
||||
.attr('x', startX)
|
||||
.attr('y', startY)
|
||||
.attr('width', width)
|
||||
// Adding clipPathBuffer to height so it doesn't
|
||||
// cutoff the lower part of the chart
|
||||
.attr('height', height + clipPathBuffer);
|
||||
.attr('x', startX)
|
||||
.attr('y', startY)
|
||||
.attr('width', width)
|
||||
// Adding clipPathBuffer to height so it doesn't
|
||||
// cutoff the lower part of the chart
|
||||
.attr('height', height + clipPathBuffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders d3 visualization
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Creates the line chart
|
||||
*/
|
||||
LineChart.prototype.draw = function () {
|
||||
// Attributes
|
||||
var self = this;
|
||||
var $elem = $(this.chartEl);
|
||||
var margin = this._attr.margin;
|
||||
|
@ -229,41 +268,30 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
// Get the width and height
|
||||
width = elWidth - margin.left - margin.right;
|
||||
height = elHeight - margin.top - margin.bottom;
|
||||
|
||||
// if height or width < 20 or NaN, throw error
|
||||
if (_.isNaN(width) || width < minWidth || _.isNaN(height) || height < minHeight) {
|
||||
throw new Error(chartToSmallError);
|
||||
}
|
||||
|
||||
// Select the current DOM element
|
||||
div = d3.select(el);
|
||||
|
||||
// Create the canvas for the visualization
|
||||
svg = div.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
||||
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
||||
|
||||
// add clipPath to hide circles when they go out of bounds
|
||||
self.addClipPath(svg, width, height);
|
||||
|
||||
// addBrush canvas
|
||||
self.events.addBrush(xScale, svg);
|
||||
|
||||
// add lines
|
||||
lines = self.addLines(svg, data.series);
|
||||
|
||||
// add circles
|
||||
circles = self.addCircles(svg, layers);
|
||||
|
||||
// add click and hover events to circles
|
||||
self.addCircleEvents(circles);
|
||||
|
||||
// chart base line
|
||||
var line = svg
|
||||
.append('line')
|
||||
.attr('x1', startLineX)
|
||||
|
|
|
@ -5,12 +5,17 @@ define(function (require) {
|
|||
|
||||
var Chart = Private(require('components/vislib/visualizations/_chart'));
|
||||
var errors = require('errors');
|
||||
|
||||
// Dynamically adds css file
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
/*
|
||||
* Column chart visualization => vertical bars, stacked bars
|
||||
/**
|
||||
* Pie Chart Visualization
|
||||
*
|
||||
* @class PieChart
|
||||
* @constructor
|
||||
* @extends Chart
|
||||
* @param handler {Object} Reference to the Handler Class Constructor
|
||||
* @param el {HTMLElement} HTML element to which the chart will be appended
|
||||
* @param chartData {Object} Elasticsearch query results for this specific chart
|
||||
*/
|
||||
_(PieChart).inherits(Chart);
|
||||
function PieChart(handler, chartEl, chartData) {
|
||||
|
@ -22,11 +27,19 @@ define(function (require) {
|
|||
this.columns = handler.data.data.raw.columns;
|
||||
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
isDonut: handler._attr.isDonut || false,
|
||||
getSize: function (d) { return d.size; },
|
||||
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Events to SVG paths
|
||||
*
|
||||
* @method addPathEvents
|
||||
* @param path {D3.Selection} Reference to SVG path
|
||||
* @returns {D3.Selection} SVG path with event listeners attached
|
||||
*/
|
||||
PieChart.prototype.addPathEvents = function (path) {
|
||||
var events = this.events;
|
||||
var dispatch = this.events._attr.dispatch;
|
||||
|
@ -50,6 +63,16 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds pie paths to SVG
|
||||
*
|
||||
* @method addPath
|
||||
* @param width {Number} Width of SVG
|
||||
* @param height {Number} Height of SVG
|
||||
* @param svg {HTMLElement} Chart SVG
|
||||
* @param slices {Object} Chart data
|
||||
* @returns {D3.Selection} SVG with paths attached
|
||||
*/
|
||||
PieChart.prototype.addPath = function (width, height, svg, slices) {
|
||||
var isDonut = this._attr.isDonut;
|
||||
var radius = Math.min(width, height) / 2;
|
||||
|
@ -113,7 +136,6 @@ define(function (require) {
|
|||
return color(fieldFormatter(d.name));
|
||||
});
|
||||
|
||||
// Add tooltip
|
||||
if (isTooltip) {
|
||||
path.call(tooltip.render());
|
||||
}
|
||||
|
@ -121,6 +143,12 @@ define(function (require) {
|
|||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders d3 visualization
|
||||
*
|
||||
* @method draw
|
||||
* @returns {Function} Creates the pie chart
|
||||
*/
|
||||
PieChart.prototype.draw = function () {
|
||||
var self = this;
|
||||
var isEvents = this._attr.addEvents;
|
||||
|
@ -145,10 +173,8 @@ define(function (require) {
|
|||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
|
||||
// add pie slices
|
||||
var path = self.addPath(width, height, svg, slices);
|
||||
|
||||
// add events to bars
|
||||
if (isEvents) {
|
||||
self.addPathEvents(path);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
define(function (require) {
|
||||
return function VisTypeFactory(Private) {
|
||||
// visLib visualization types
|
||||
|
||||
/**
|
||||
* Provides the visualizations for the visLib
|
||||
*
|
||||
* @module visLib
|
||||
* @submodule VisTypeFactory
|
||||
* @param Private {Object} Loads any function as an angular module
|
||||
* @return {Function} Returns an Object of Visualization classes
|
||||
*/
|
||||
return {
|
||||
histogram: Private(require('components/vislib/visualizations/column_chart')),
|
||||
pie: Private(require('components/vislib/visualizations/pie_chart')),
|
||||
|
|
|
@ -3,10 +3,18 @@
|
|||
</div>
|
||||
|
||||
<div ng-show="spyMode.name === 'request'">
|
||||
<label>
|
||||
Elasticsearch request body  
|
||||
<kbn-clipboard copy="history.req | json"></kbn-clipboard>
|
||||
</label>
|
||||
<pre>{{history.req | json}}</pre>
|
||||
</div>
|
||||
|
||||
<div ng-show="spyMode.name === 'response'">
|
||||
<label>
|
||||
Elasticsearch response body  
|
||||
<kbn-clipboard copy="history.resp | json"></kbn-clipboard>
|
||||
</label>
|
||||
<pre>{{history.resp | json}}</pre>
|
||||
</div>
|
||||
|
||||
|
@ -17,4 +25,4 @@
|
|||
<td>{{pair[1]}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div
|
|
@ -2,6 +2,7 @@ define(function (require) {
|
|||
return function VisSpyReqRespStats() {
|
||||
var _ = require('lodash');
|
||||
var reqRespStatsHTML = require('text!components/visualize/spy/_req_resp_stats.html');
|
||||
require('components/clipboard/clipboard');
|
||||
|
||||
var linkReqRespStats = function ($scope, config) {
|
||||
$scope.$watch('searchSource.history.length', function () {
|
||||
|
|
|
@ -23,7 +23,6 @@ define(function (require) {
|
|||
require('directives/style_compile');
|
||||
require('directives/rows');
|
||||
|
||||
|
||||
require('angular-bootstrap');
|
||||
require('services/private');
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ define(function (require) {
|
|||
template: html,
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: true,
|
||||
link: function ($scope, $el, attr) {
|
||||
$scope.text = attr.text;
|
||||
$scope.placement = attr.placement || 'top';
|
||||
|
|
16
src/kibana/filters/label.js
Normal file
16
src/kibana/filters/label.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
define(function (require) {
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.filter('label', function () {
|
||||
return function (str) {
|
||||
var words = str.split(' ');
|
||||
return words.map(capFirst).join(' ');
|
||||
};
|
||||
});
|
||||
|
||||
function capFirst(str) {
|
||||
var i = str[0];
|
||||
var r = new RegExp(i, 'i');
|
||||
return str.replace(r, i.toUpperCase());
|
||||
}
|
||||
});
|
|
@ -12,16 +12,21 @@ define(function (require) {
|
|||
require('angular-route');
|
||||
require('angular-bindonce');
|
||||
|
||||
// Seems bad?
|
||||
window.ZeroClipboard = require('zeroclipboard');
|
||||
require('ng-clip');
|
||||
|
||||
var configFile = JSON.parse(require('text!config'));
|
||||
|
||||
var kibana = modules.get('kibana', [
|
||||
// list external requirements here
|
||||
'elasticsearch',
|
||||
'pasvaz.bindonce',
|
||||
'ngRoute'
|
||||
'ngRoute',
|
||||
'ngClipboard'
|
||||
]);
|
||||
|
||||
configFile.elasticsearch = ('http://' + window.location.hostname + '/elasticsearch/');
|
||||
configFile.elasticsearch = (window.location.protocol + '//' + window.location.hostname + '/elasticsearch/');
|
||||
|
||||
kibana
|
||||
// This stores the Kibana revision number, @REV@ is replaced by grunt.
|
||||
|
@ -37,7 +42,10 @@ define(function (require) {
|
|||
// When we need to identify the current session of the app, ef shard preference
|
||||
.constant('sessionId', Date.now())
|
||||
// attach the route manager's known routes
|
||||
.config(routes.config);
|
||||
.config(routes.config)
|
||||
.config(['ngClipProvider', function (ngClipProvider) {
|
||||
ngClipProvider.setPath('bower_components/zeroclipboard/dist/ZeroClipboard.swf');
|
||||
}]);
|
||||
|
||||
// setup routes
|
||||
routes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div
|
||||
<span
|
||||
tooltip="{{text}}"
|
||||
tooltip-placement="{{placement}}"
|
||||
tooltip-popup-delay="{{delay}}"
|
||||
tooltip-append-to-body="{{appendToBody}}"
|
||||
ng-transclude></div>
|
||||
ng-transclude></span>
|
|
@ -3,7 +3,6 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
|
||||
require('gridster');
|
||||
require('css!bower_components/gridster/dist/jquery.gridster.css');
|
||||
|
||||
var app = require('modules').get('app/dashboard');
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ define(function (require) {
|
|||
require('components/config/config');
|
||||
require('components/notify/notify');
|
||||
require('components/typeahead/typeahead');
|
||||
require('components/clipboard/clipboard');
|
||||
|
||||
|
||||
require('plugins/dashboard/directives/grid');
|
||||
require('plugins/dashboard/directives/panel');
|
||||
|
@ -151,7 +153,8 @@ define(function (require) {
|
|||
return {
|
||||
link: $location.absUrl(),
|
||||
// This sucks, but seems like the cleanest way. Uhg.
|
||||
embed: $location.absUrl().replace('?', '?embed&')
|
||||
embed: '<iframe src="' + $location.absUrl().replace('?', '?embed&') +
|
||||
'" height="600" width="800"></iframe>'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<form role="form" class="vis-share">
|
||||
<div class="form-group">
|
||||
<label>Embed this dashboard. <small>Copy code into your html source. Note all clients must still be able to access kibana</small></label>
|
||||
<div class="form-control" disabled><iframe src="{{opts.shareData().embed}}" height="600" width="800"></iframe></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Share a link</label>
|
||||
<div class="form-control" disabled>{{opts.shareData().link}}</div>
|
||||
</div>
|
||||
<p>
|
||||
<div class="input-group">
|
||||
<label>Embed this dashboard. <kbn-clipboard copy="opts.shareData().embed"></kbn-clipboard> <small>Add to your html source. Note all clients must still be able to access kibana</small></label>
|
||||
<div class="form-control" disabled>{{opts.shareData().embed}}</div>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div class="input-group">
|
||||
<label>Share a link <kbn-clipboard copy="opts.shareData().link"></kbn-clipboard></i></a></label>
|
||||
<div class="form-control" disabled>{{opts.shareData().link}}</div>
|
||||
</div>
|
||||
</p>
|
||||
</form>
|
|
@ -82,7 +82,7 @@ define(function (require) {
|
|||
|
||||
var stateDefaults = {
|
||||
query: initialQuery || '',
|
||||
columns: ['_source'],
|
||||
columns: savedSearch.columns || ['_source'],
|
||||
index: $scope.searchSource.get('index').id || config.get('defaultIndex'),
|
||||
interval: 'auto',
|
||||
filters: _.cloneDeep($scope.searchSource.get('filter'))
|
||||
|
@ -232,6 +232,7 @@ define(function (require) {
|
|||
return $scope.updateDataSource()
|
||||
.then(function () {
|
||||
savedSearch.id = savedSearch.title;
|
||||
savedSearch.columns = $scope.state.columns;
|
||||
|
||||
return savedSearch.save()
|
||||
.then(function () {
|
||||
|
@ -529,7 +530,7 @@ define(function (require) {
|
|||
_.each(value, function (clause) {
|
||||
var previous = _.find(filters, function (item) {
|
||||
if (item && item.query) {
|
||||
return item.query.match[field] === { query: clause, type: 'phrase' };
|
||||
return item.query.match[field].query === clause;
|
||||
} else if (item && item.exists && field === '_exists_') {
|
||||
return item.exists.field === clause;
|
||||
} else if (item && item.missing && field === '_missing_') {
|
||||
|
|
|
@ -20,12 +20,14 @@ define(function (require) {
|
|||
mapping: {
|
||||
title: 'string',
|
||||
description: 'string',
|
||||
hits: 'integer'
|
||||
hits: 'integer',
|
||||
columns: 'string'
|
||||
},
|
||||
|
||||
defaults: {
|
||||
title: 'New Saved Search',
|
||||
description: '',
|
||||
columns: [],
|
||||
hits: 0
|
||||
},
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ define(function (require) {
|
|||
|
||||
// X-axis description
|
||||
chart.xAxisLabel = colX.label;
|
||||
|
||||
// identify how the x-axis is ordered
|
||||
if (aggX && aggX.ordered && aggX.ordered.date) {
|
||||
chart.xAxisFormatter = (function () {
|
||||
var bounds = timefilter.getBounds();
|
||||
|
@ -62,15 +64,19 @@ define(function (require) {
|
|||
};
|
||||
}());
|
||||
|
||||
var timeBounds = timefilter.getBounds();
|
||||
chart.ordered = {
|
||||
date: true,
|
||||
min: timeBounds.min.valueOf(),
|
||||
max: timeBounds.max.valueOf(),
|
||||
interval: interval.toMs(colX.params.interval)
|
||||
};
|
||||
|
||||
if (colX.aggConfig.vis.indexPattern.timeFieldName) {
|
||||
var timeBounds = timefilter.getBounds();
|
||||
chart.ordered.min = timeBounds.min.valueOf();
|
||||
chart.ordered.max = timeBounds.max.valueOf();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (!chart.ordered) {
|
||||
chart.xAxisFormatter = colX.field && colX.field.format.convert;
|
||||
chart.ordered = aggX && aggX.ordered && {};
|
||||
if (aggX !== false && colX && colX.params && colX.params.interval) {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
define(function (require) {
|
||||
require('modules')
|
||||
.get('app/visualize')
|
||||
.get('app/visualize', ['localytics.directives'])
|
||||
.directive('visEditorAgg', function ($compile, $parse, Private, Notifier) {
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var aggTypes = Private(require('components/agg_types/index'));
|
||||
var aggSelectHtml = require('text!plugins/visualize/editor/agg_select.html');
|
||||
var advancedToggleHtml = require('text!apps/visualize/partials/advanced_toggle.html');
|
||||
|
||||
var chosen = require('angular-chosen');
|
||||
require('plugins/visualize/editor/agg_param');
|
||||
|
||||
var notify = new Notifier({
|
||||
|
@ -28,6 +30,7 @@ define(function (require) {
|
|||
link: function ($scope, $el) {
|
||||
$scope.aggTypeOptions = aggTypes.byType[$scope.groupName];
|
||||
$scope.editorOpen = $scope.agg.brandNew;
|
||||
$scope.advancedToggled = false;
|
||||
|
||||
$scope.$watchMulti([
|
||||
'$index',
|
||||
|
@ -54,8 +57,8 @@ define(function (require) {
|
|||
var $aggSelect = $(aggSelectHtml).appendTo($editorContainer);
|
||||
$compile($aggSelect)($scope);
|
||||
|
||||
// params for the selected agg, these are rebuilt every time the agg changes
|
||||
var $aggParamEditors;
|
||||
// params for the selected agg, these are rebuilt every time the agg in $aggSelect changes
|
||||
var $aggParamEditors; // container for agg type param editors
|
||||
var $aggParamEditorsScope;
|
||||
$scope.$watch('agg.type', function updateAggParamEditor(newType, oldType) {
|
||||
if ($aggParamEditors) {
|
||||
|
@ -81,25 +84,59 @@ define(function (require) {
|
|||
|
||||
if (!type) return;
|
||||
|
||||
var editors = type.params.map(function (param, i) {
|
||||
if (!param.editor) return;
|
||||
var aggParamHTML = {
|
||||
basic: [],
|
||||
advanced: []
|
||||
};
|
||||
|
||||
return $('<vis-agg-param-editor>')
|
||||
.attr({
|
||||
'agg-type': 'agg.type',
|
||||
'agg-config': 'agg',
|
||||
'agg-param': 'agg.type.params[' + i + ']',
|
||||
'params': 'agg.params'
|
||||
})
|
||||
.append(param.editor)
|
||||
.get(0);
|
||||
}).filter(Boolean);
|
||||
// build collection of agg params html
|
||||
type.params.forEach(function (param, i) {
|
||||
var aggParam;
|
||||
var type = 'basic';
|
||||
if (param.advanced) type = 'advanced';
|
||||
|
||||
if (aggParam = getAggParamHTML(param, i)) {
|
||||
aggParamHTML[type].push(aggParam);
|
||||
}
|
||||
});
|
||||
|
||||
// compile the paramEditors html elements
|
||||
var paramEditors = aggParamHTML.basic;
|
||||
|
||||
if (aggParamHTML.advanced.length) {
|
||||
paramEditors.push($(advancedToggleHtml).get(0));
|
||||
paramEditors = paramEditors.concat(aggParamHTML.advanced);
|
||||
}
|
||||
|
||||
$aggParamEditors = $(editors).appendTo($editorContainer);
|
||||
$aggParamEditorsScope = $scope.$new();
|
||||
$aggParamEditors = $(paramEditors).appendTo($editorContainer);
|
||||
$compile($aggParamEditors)($aggParamEditorsScope);
|
||||
});
|
||||
|
||||
// build HTML editor given an aggParam and index
|
||||
function getAggParamHTML(param, idx) {
|
||||
// don't show params without an editor
|
||||
if (!param.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var attrs = {
|
||||
'agg-type': 'agg.type',
|
||||
'agg-config': 'agg',
|
||||
'params': 'agg.params'
|
||||
};
|
||||
|
||||
attrs['agg-param'] = 'agg.type.params[' + idx + ']';
|
||||
if (param.advanced) {
|
||||
attrs['ng-show'] = 'advancedToggled';
|
||||
}
|
||||
|
||||
return $('<vis-agg-param-editor>')
|
||||
.attr(attrs)
|
||||
.append(param.editor)
|
||||
.get(0);
|
||||
}
|
||||
|
||||
// generic child scope creation, for both schema and agg
|
||||
function editorScope() {
|
||||
var $editorScope = $scope.$new();
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
list="group">
|
||||
</nesting-indicator>
|
||||
|
||||
<!-- agg.html -->
|
||||
<!-- agg.html - controls for aggregation -->
|
||||
<vis-editor-agg
|
||||
vis="vis"
|
||||
group="group"
|
||||
|
|
|
@ -3,8 +3,11 @@ define(function (require) {
|
|||
require('plugins/visualize/editor/sidebar');
|
||||
require('plugins/visualize/editor/agg_filter');
|
||||
|
||||
|
||||
require('directives/saved_object_finder');
|
||||
require('components/visualize/visualize');
|
||||
require('components/clipboard/clipboard');
|
||||
|
||||
require('filters/uriescape');
|
||||
|
||||
require('routes')
|
||||
|
@ -172,7 +175,8 @@ define(function (require) {
|
|||
return {
|
||||
link: $location.absUrl(),
|
||||
// This sucks, but seems like the cleanest way. Uhg.
|
||||
embed: $location.absUrl().replace('?', '?embed&')
|
||||
embed: '<iframe src="' + $location.absUrl().replace('?', '?embed&') +
|
||||
'" height="600" width="800"></iframe>'
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
<form role="form" class="vis-share">
|
||||
<div class="form-group">
|
||||
<label>Embed this visualization. <small>Copy code into your html source. Note all clients must still be able to access kibana</small></label>
|
||||
<div class="form-control" disabled><iframe src="{{conf.shareData().embed}}" height="400" width="600"></iframe></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Share a link</label>
|
||||
<div class="form-control" disabled>{{conf.shareData().link}}</div>
|
||||
</div>
|
||||
<p>
|
||||
<div class="form-group">
|
||||
<label>Embed this visualization.
|
||||
<kbn-clipboard copy="conf.shareData().embed"></kbn-clipboard>
|
||||
|
||||
<small>Add to your html source. Note all clients must still be able to access kibana</small>
|
||||
</label>
|
||||
<div class="form-control" disabled>{{conf.shareData().embed}}</div>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div class="form-group">
|
||||
<label>Share a link <kbn-clipboard copy="conf.shareData().link"></kbn-clipboard></label>
|
||||
<div class="form-control" disabled>{{conf.shareData().link}}</div>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
</form>
|
|
@ -161,6 +161,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.regex .flags {
|
||||
.docs {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-form-row {
|
||||
|
|
|
@ -14,10 +14,12 @@ require.config({
|
|||
'angular-elastic': 'bower_components/angular-elastic/elastic',
|
||||
'angular-route': 'bower_components/angular-route/angular-route',
|
||||
'angular-ui-ace': 'bower_components/angular-ui-ace/ui-ace',
|
||||
'angular-chosen': 'bower_components/angular-chosen-localytics/chosen',
|
||||
ace: 'bower_components/ace-builds/src-noconflict/ace',
|
||||
angular: 'bower_components/angular/angular',
|
||||
async: 'bower_components/async/lib/async',
|
||||
bower_components: 'bower_components',
|
||||
chosen: 'bower_components/chosen/chosen.jquery.min',
|
||||
css: 'bower_components/require-css/css',
|
||||
d3: 'bower_components/d3/d3',
|
||||
elasticsearch: 'bower_components/elasticsearch/elasticsearch.angular',
|
||||
|
@ -29,7 +31,9 @@ require.config({
|
|||
jsonpath: 'bower_components/jsonpath/lib/jsonpath',
|
||||
lodash_src: 'bower_components/lodash/dist/lodash',
|
||||
moment: 'bower_components/moment/moment',
|
||||
text: 'bower_components/requirejs-text/text'
|
||||
'ng-clip': 'bower_components/ng-clip/src/ngClip',
|
||||
text: 'bower_components/requirejs-text/text',
|
||||
zeroclipboard: 'bower_components/zeroclipboard/dist/ZeroClipboard'
|
||||
},
|
||||
shim: {
|
||||
angular: {
|
||||
|
@ -39,12 +43,14 @@ require.config({
|
|||
jsonpath: {
|
||||
exports: 'jsonPath'
|
||||
},
|
||||
gridster: ['jquery'],
|
||||
gridster: ['jquery', 'css!bower_components/gridster/dist/jquery.gridster.css'],
|
||||
'angular-chosen': ['jquery', 'chosen'],
|
||||
'angular-route': ['angular'],
|
||||
'elasticsearch': ['angular'],
|
||||
'angular-bootstrap': ['angular'],
|
||||
'angular-bindonce': ['angular'],
|
||||
'angular-ui-ace': ['angular', 'ace'],
|
||||
'ng-clip': ['angular', 'zeroclipboard'],
|
||||
inflection: {
|
||||
exports: 'inflection'
|
||||
},
|
||||
|
|
|
@ -70,8 +70,8 @@ define(function (require) {
|
|||
var bounds = this.getBounds();
|
||||
filter = {range : {}};
|
||||
filter.range[timefield.name] = {
|
||||
gte: bounds.min,
|
||||
lte: bounds.max
|
||||
gte: bounds.min.valueOf(),
|
||||
lte: bounds.max.valueOf()
|
||||
};
|
||||
}
|
||||
return filter;
|
||||
|
|
33
src/kibana/styles/_chosen.less
Normal file
33
src/kibana/styles/_chosen.less
Normal file
|
@ -0,0 +1,33 @@
|
|||
@import "../bower_components/bootstrap-chosen/bootstrap-chosen.less";
|
||||
@chosen-sprite-path: "/bower_components/bootstrap-chosen/chosen-sprite.png";
|
||||
@chosen-sprite-retina-path: "/bower_components/bootstrap-chosen/chosen-sprite@2x.png";
|
||||
@chosen-border: 2px solid @input-border;
|
||||
@chosen-focus-border: 2px solid @input-border-focus;
|
||||
@chosen-box-shadow: none;
|
||||
@chosen-focus-box-shadow: none;
|
||||
@chosen-drop-box-shadow: none;
|
||||
@chosen-drop-border: @input-border-focus;
|
||||
|
||||
// focus rules used to match our theme
|
||||
.chosen-container-active:focus {
|
||||
.chosen-single,
|
||||
&.chosen-with-drop .chosen-single,
|
||||
.chosen-choices {
|
||||
.box-shadow(@chosen-focus-box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the gradient on the choices
|
||||
.chosen-container-multi {
|
||||
.chosen-choices {
|
||||
.search-field {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 2px;
|
||||
margin-left: 6px;
|
||||
|
||||
input {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,4 +16,7 @@
|
|||
@sidebar-active-color: @component-active-color;
|
||||
@sidebar-active-bg: @component-active-bg;
|
||||
@sidebar-active-hover-bg: @component-active-bg;
|
||||
@sidebar-active-hover-color: @component-active-color;
|
||||
@sidebar-active-hover-color: @component-active-color;
|
||||
|
||||
@action-add: desaturate(@btn-success-bg, 30%);
|
||||
@action-remove: desaturate(@btn-danger-bg, 30%);
|
|
@ -13,6 +13,9 @@
|
|||
// bootstrap modifications
|
||||
@import "./theme/_theme.less";
|
||||
|
||||
// chosen
|
||||
@import "./_chosen.less";
|
||||
|
||||
// call outs
|
||||
@import "./_callout.less";
|
||||
|
||||
|
@ -132,7 +135,7 @@ notifications {
|
|||
margin-bottom: 0px!important;
|
||||
}
|
||||
|
||||
[ng-click], [href], [confirm-click] {
|
||||
[ng-click], [clip-copy], [href], [confirm-click] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
# Kibana is served by a backend server. This controls which port to use.
|
||||
port: 5601
|
||||
|
||||
# The host to bind the server to
|
||||
host: "0.0.0.0"
|
||||
|
||||
# The Elasticsearch instance to use for all your queries
|
||||
elasticsearch: "http://localhost:9200"
|
||||
|
||||
# Kibana uses an index in Elasticsearch to store saved searches, visualizations
|
||||
# and dashboards. It will create an new index if it doesn't already exist.
|
||||
kibanaIndex: "kibana-int"
|
||||
kibanaIndex: ".kibana"
|
||||
|
||||
# The default application to laad.
|
||||
defaultAppId: "discover"
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
'css!screencast_reporter_css'
|
||||
], function (ScreencastReporter) {
|
||||
mocha.reporter(ScreencastReporter);
|
||||
done()
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,7 @@
|
|||
require([
|
||||
'kibana',
|
||||
'sinon/sinon',
|
||||
'angular',
|
||||
'specs/apps/discover/hit_sort_fn',
|
||||
'specs/apps/discover/directives/table',
|
||||
'specs/apps/discover/directives/discover_field',
|
||||
|
@ -111,35 +112,37 @@
|
|||
'specs/state_management/app_state',
|
||||
'specs/utils/diff_object',
|
||||
'specs/factories/events',
|
||||
'specs/vislib/color',
|
||||
'specs/vislib/zero_injection',
|
||||
'specs/vislib/labels',
|
||||
'specs/vislib/x_axis',
|
||||
'specs/vislib/y_axis',
|
||||
'specs/vislib/axis_title',
|
||||
'specs/vislib/chart_title',
|
||||
'specs/vislib/layout_types',
|
||||
'specs/vislib/vis_types',
|
||||
'specs/vislib/splits',
|
||||
'specs/vislib/layout',
|
||||
'specs/vislib/_chart',
|
||||
'specs/vislib/column_layout',
|
||||
'specs/vislib/components/color',
|
||||
'specs/vislib/components/zero_injection',
|
||||
'specs/vislib/components/labels',
|
||||
'specs/vislib/lib/x_axis',
|
||||
'specs/vislib/lib/y_axis',
|
||||
'specs/vislib/lib/axis_title',
|
||||
'specs/vislib/lib/chart_title',
|
||||
'specs/vislib/lib/layout_types',
|
||||
'specs/vislib/lib/splits',
|
||||
'specs/vislib/lib/layout',
|
||||
'specs/vislib/lib/tooltip',
|
||||
'specs/vislib/lib/handler',
|
||||
'specs/vislib/lib/_error_handler',
|
||||
'specs/vislib/lib/data',
|
||||
'specs/vislib/lib/column_layout',
|
||||
'specs/vislib/lib/resize_checker',
|
||||
'specs/vislib/visualizations/_chart',
|
||||
'specs/vislib/visualizations/vis_types',
|
||||
'specs/vislib/index',
|
||||
'specs/vislib/vis',
|
||||
'specs/vislib/tooltip',
|
||||
'specs/vislib/handler',
|
||||
'specs/vislib/_error_handler',
|
||||
'specs/vislib/data',
|
||||
'specs/vislib/resize_checker',
|
||||
'specs/utils/diff_time_picker_vals',
|
||||
'specs/factories/events',
|
||||
'specs/index_patterns/_flatten_search_response',
|
||||
'specs/utils/indexed_array/index',
|
||||
'specs/directives/filter_bar',
|
||||
'specs/components/agg_types/index',
|
||||
'specs/components/agg_types/param_types/index',
|
||||
'specs/components/vis/index',
|
||||
'specs/components/reflow_watcher',
|
||||
'specs/registry/index'
|
||||
'specs/registry/index',
|
||||
'specs/components/clipboard'
|
||||
], function (kibana, sinon) {
|
||||
kibana.load(function () {
|
||||
var xhr = sinon.useFakeXMLHttpRequest();
|
||||
|
|
|
@ -6,6 +6,7 @@ define(function (require) {
|
|||
var BaseAggParam;
|
||||
var FieldAggParam;
|
||||
var OptionedAggParam;
|
||||
var RegexAggParam;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
// stub out the param classes before we get the AggParams
|
||||
|
@ -16,6 +17,7 @@ define(function (require) {
|
|||
BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
FieldAggParam = Private(require('components/agg_types/param_types/field'));
|
||||
OptionedAggParam = Private(require('components/agg_types/param_types/optioned'));
|
||||
RegexAggParam = Private(require('components/agg_types/param_types/regex'));
|
||||
}));
|
||||
|
||||
describe('constructor args', function () {
|
||||
|
@ -50,23 +52,30 @@ define(function (require) {
|
|||
|
||||
expect(aggParams).to.have.length(1);
|
||||
expect(aggParams[0]).to.be.a(FieldAggParam);
|
||||
expect(aggParams[0]).to.be.a(BaseAggParam);
|
||||
});
|
||||
|
||||
it('Uses the OptionedAggParam class for params with defined options', function () {
|
||||
it('Uses the OptionedAggParam class for params of type "optioned"', function () {
|
||||
var aggParams = new AggParams([
|
||||
{
|
||||
name: 'interval',
|
||||
options: [
|
||||
{ display: 'Automatic', val: 'auto' },
|
||||
{ display: '2 Hours', val: '2h' }
|
||||
]
|
||||
type: 'optioned'
|
||||
}
|
||||
]);
|
||||
|
||||
expect(aggParams).to.have.length(1);
|
||||
expect(aggParams[0]).to.be.a(OptionedAggParam);
|
||||
expect(aggParams[0]).to.be.a(BaseAggParam);
|
||||
});
|
||||
|
||||
it('Uses the RegexAggParam class for params of type "regex"', function () {
|
||||
var aggParams = new AggParams([
|
||||
{
|
||||
name: 'exclude',
|
||||
type: 'regex'
|
||||
}
|
||||
]);
|
||||
|
||||
expect(aggParams).to.have.length(1);
|
||||
expect(aggParams[0]).to.be.a(RegexAggParam);
|
||||
});
|
||||
|
||||
it('Always converts the params to a BaseAggParam', function () {
|
||||
|
|
25
test/unit/specs/components/agg_types/param_types/_field.js
Normal file
25
test/unit/specs/components/agg_types/param_types/_field.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
define(function (require) {
|
||||
return ['Regex', function () {
|
||||
var _ = require('lodash');
|
||||
|
||||
var BaseAggParam;
|
||||
var FieldAggParam;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
// fetch out deps
|
||||
beforeEach(inject(function (Private) {
|
||||
BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
FieldAggParam = Private(require('components/agg_types/param_types/field'));
|
||||
}));
|
||||
|
||||
describe('constructor', function () {
|
||||
it('it is an instance of BaseAggParam', function () {
|
||||
var aggParam = new FieldAggParam({
|
||||
name: 'field'
|
||||
});
|
||||
|
||||
expect(aggParam).to.be.a(BaseAggParam);
|
||||
});
|
||||
});
|
||||
}];
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
define(function (require) {
|
||||
return ['Regex', function () {
|
||||
var _ = require('lodash');
|
||||
|
||||
var BaseAggParam;
|
||||
var OptionedAggParam;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
// fetch out deps
|
||||
beforeEach(inject(function (Private) {
|
||||
BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
OptionedAggParam = Private(require('components/agg_types/param_types/optioned'));
|
||||
}));
|
||||
|
||||
describe('constructor', function () {
|
||||
it('it is an instance of BaseAggParam', function () {
|
||||
var aggParam = new OptionedAggParam({
|
||||
name: 'some_param',
|
||||
type: 'optioned'
|
||||
});
|
||||
|
||||
expect(aggParam).to.be.a(BaseAggParam);
|
||||
});
|
||||
});
|
||||
}];
|
||||
});
|
76
test/unit/specs/components/agg_types/param_types/_regex.js
Normal file
76
test/unit/specs/components/agg_types/param_types/_regex.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
define(function (require) {
|
||||
return ['Regex', function () {
|
||||
var _ = require('lodash');
|
||||
|
||||
var BaseAggParam;
|
||||
var RegexAggParam;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
// fetch out deps
|
||||
beforeEach(inject(function (Private) {
|
||||
BaseAggParam = Private(require('components/agg_types/param_types/base'));
|
||||
RegexAggParam = Private(require('components/agg_types/param_types/regex'));
|
||||
}));
|
||||
|
||||
describe('constructor', function () {
|
||||
it('should be an instance of BaseAggParam', function () {
|
||||
var aggParam = new RegexAggParam({
|
||||
name: 'some_param',
|
||||
type: 'regex'
|
||||
});
|
||||
|
||||
expect(aggParam).to.be.a(BaseAggParam);
|
||||
expect(aggParam).to.have.property('write');
|
||||
});
|
||||
});
|
||||
|
||||
describe('write results', function () {
|
||||
var aggParam;
|
||||
var aggConfig = { params: {} };
|
||||
var output = { params: {} };
|
||||
var paramName = 'exclude';
|
||||
|
||||
beforeEach(function () {
|
||||
aggParam = new RegexAggParam({
|
||||
name: paramName,
|
||||
type: 'regex'
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include param in output', function () {
|
||||
aggConfig.params[paramName] = {
|
||||
pattern: ''
|
||||
};
|
||||
|
||||
aggParam.write(aggConfig, output);
|
||||
expect(output).to.be.an('object');
|
||||
expect(output).to.have.property('params');
|
||||
expect(output.params).not.to.have.property(paramName);
|
||||
});
|
||||
|
||||
it('should include param in output', function () {
|
||||
aggConfig.params[paramName] = {
|
||||
pattern: 'testing'
|
||||
};
|
||||
|
||||
aggParam.write(aggConfig, output);
|
||||
expect(output.params).to.have.property(paramName);
|
||||
expect(output.params[paramName]).to.eql({ pattern: 'testing' });
|
||||
expect(output.params[paramName]).not.to.have.property('flags');
|
||||
});
|
||||
|
||||
it('should include flags', function () {
|
||||
aggConfig.params[paramName] = {
|
||||
pattern: 'testing',
|
||||
flags: [ 'TEST1', 'TEST2', 'TEST_RED', 'TEST_BLUE' ]
|
||||
};
|
||||
|
||||
aggParam.write(aggConfig, output);
|
||||
expect(output.params).to.have.property(paramName);
|
||||
expect(output.params[paramName]).to.have.property('flags');
|
||||
expect(typeof output.params[paramName].flags).to.be('string');
|
||||
expect(output.params[paramName].flags).to.be('TEST1|TEST2|TEST_RED|TEST_BLUE');
|
||||
});
|
||||
});
|
||||
}];
|
||||
});
|
11
test/unit/specs/components/agg_types/param_types/index.js
Normal file
11
test/unit/specs/components/agg_types/param_types/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
define(function (require) {
|
||||
describe('ParamTypes', function () {
|
||||
var childSuites = [
|
||||
require('specs/components/agg_types/param_types/_field'),
|
||||
require('specs/components/agg_types/param_types/_optioned'),
|
||||
require('specs/components/agg_types/param_types/_regex')
|
||||
].forEach(function (s) {
|
||||
describe(s[0], s[1]);
|
||||
});
|
||||
});
|
||||
});
|
71
test/unit/specs/components/clipboard.js
Normal file
71
test/unit/specs/components/clipboard.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('sinon/sinon');
|
||||
var $ = require('jquery');
|
||||
|
||||
require('components/clipboard/clipboard');
|
||||
|
||||
describe('Clipboard directive', function () {
|
||||
|
||||
var $scope, $rootScope, $compile, $interpolate, el, tips;
|
||||
|
||||
beforeEach(function (done) {
|
||||
// load the application
|
||||
module('kibana');
|
||||
|
||||
inject(function (_$rootScope_, _$compile_, _$interpolate_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
$interpolate = _$interpolate_;
|
||||
$rootScope.text = 'foo';
|
||||
|
||||
el = $compile('<kbn-clipboard copy="text"></kbn-clipboard>')($rootScope);
|
||||
|
||||
$scope = el.scope();
|
||||
$scope.$digest();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should contain an element with clip-copy', function () {
|
||||
var clip = el.find('[clip-copy]');
|
||||
expect(clip).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should have a tooltip', function () {
|
||||
var clip = el.find('[tooltip]');
|
||||
expect(clip).to.have.length(1);
|
||||
|
||||
var clipText = $interpolate($(clip).attr('tooltip'))();
|
||||
expect(clipText).to.be('Copy to clipboard');
|
||||
|
||||
});
|
||||
|
||||
it('should change the tooltip text when clicked, back when mouse leaves', function () {
|
||||
el.mouseenter();
|
||||
el.click();
|
||||
$scope.$digest();
|
||||
|
||||
var clipText = $interpolate($('[tooltip]', el).attr('tooltip'))();
|
||||
expect(clipText).to.be('Copied!');
|
||||
|
||||
el.mouseleave();
|
||||
$scope.$digest();
|
||||
|
||||
clipText = $interpolate($('[tooltip]', el).attr('tooltip'))();
|
||||
expect(clipText).to.be('Copy to clipboard');
|
||||
});
|
||||
|
||||
it('should unbind all handlers on destroy', function () {
|
||||
var handlers = $._data(el.get(0), 'events');
|
||||
expect(Object.keys(handlers)).to.have.length(2);
|
||||
|
||||
$scope.$destroy();
|
||||
expect(Object.keys(handlers)).to.have.length(0);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -5,6 +5,8 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
|
||||
var toggleFilter = require('components/filter_bar/lib/toggleFilter');
|
||||
var toggleAll = require('components/filter_bar/lib/toggleAll');
|
||||
|
||||
var mapFilter = require('components/filter_bar/lib/mapFilter');
|
||||
var removeFilter = require('components/filter_bar/lib/removeFilter');
|
||||
var removeAll = require('components/filter_bar/lib/removeAll');
|
||||
|
@ -27,7 +29,7 @@ define(function (require) {
|
|||
{ query: { match: { '@tags': { query: 'test' } } } },
|
||||
{ query: { match: { '@tags': { query: 'bar' } } } },
|
||||
{ exists: { field: '@timestamp' } },
|
||||
{ missing: { field: 'host' } },
|
||||
{ missing: { field: 'host' }, disabled: true },
|
||||
]
|
||||
};
|
||||
done();
|
||||
|
@ -111,7 +113,40 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('toggleAll', function () {
|
||||
var fn;
|
||||
|
||||
beforeEach(function () {
|
||||
// This would normally be done by the directive
|
||||
$rootScope.filters = _($rootScope.state.filters)
|
||||
.filter(function (filter) {
|
||||
return filter;
|
||||
})
|
||||
.flatten(true)
|
||||
.map(mapFilter)
|
||||
.value();
|
||||
|
||||
fn = toggleAll($rootScope);
|
||||
});
|
||||
|
||||
it('should toggle all the filters', function () {
|
||||
expect(_.filter($rootScope.state.filters, 'disabled')).to.have.length(1);
|
||||
fn();
|
||||
expect(_.filter($rootScope.state.filters, 'disabled')).to.have.length(3);
|
||||
});
|
||||
|
||||
it('should disable all the filters', function () {
|
||||
expect(_.filter($rootScope.state.filters, 'disabled')).to.have.length(1);
|
||||
fn(true);
|
||||
expect(_.filter($rootScope.state.filters, 'disabled')).to.have.length(4);
|
||||
});
|
||||
|
||||
it('should enable all the filters', function () {
|
||||
expect(_.filter($rootScope.state.filters, 'disabled')).to.have.length(1);
|
||||
fn(false);
|
||||
expect(_.filter($rootScope.state.filters, 'disabled')).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
42
test/unit/specs/filters/label.js
Normal file
42
test/unit/specs/filters/label.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var faker = require('faker');
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
require('angular-route');
|
||||
|
||||
// Load kibana and its applications
|
||||
require('apps/discover/index');
|
||||
|
||||
var filter;
|
||||
|
||||
var init = function (expandable) {
|
||||
// Load the application
|
||||
module('kibana');
|
||||
|
||||
// Create the scope
|
||||
inject(function ($filter) {
|
||||
filter = $filter('label');
|
||||
});
|
||||
};
|
||||
|
||||
describe('label filter', function () {
|
||||
beforeEach(function () {
|
||||
init();
|
||||
});
|
||||
|
||||
it('should have a label filter', function () {
|
||||
expect(filter).to.not.be(null);
|
||||
});
|
||||
|
||||
it('should capitalize the first letter in a string', function () {
|
||||
expect(filter('something')).to.be('Something');
|
||||
});
|
||||
|
||||
it('should capitalize the first letter in every word', function () {
|
||||
expect(filter('foo bar fizz buzz')).to.be('Foo Bar Fizz Buzz');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -12,8 +12,14 @@ define(function (require) {
|
|||
describe('Color (main)', function () {
|
||||
var getColors;
|
||||
var arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest'];
|
||||
var str = 'test';
|
||||
var error;
|
||||
var arrayOfNumbers = [1, 2, 3, 4, 5];
|
||||
var arrayOfUndefinedValues = [undefined, undefined, undefined];
|
||||
var arrayOfObjects = [{}, {}, {}];
|
||||
var arrayOfBooleans = [true, false, true];
|
||||
var arrayOfNullValues = [null, null, null];
|
||||
var emptyObject = {};
|
||||
var nullValue = null;
|
||||
var notAValue;
|
||||
var color;
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -24,11 +30,66 @@ define(function (require) {
|
|||
inject(function (d3, Private) {
|
||||
seedColors = Private(require('components/vislib/components/color/seed_colors'));
|
||||
getColors = Private(require('components/vislib/components/color/color'));
|
||||
// error = getColors(str);
|
||||
color = getColors(arr);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an array', function () {
|
||||
expect(function () {
|
||||
getColors(200);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors('help');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(true);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(notAValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(emptyObject);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should throw an error if array is not composed of numbers, strings, or ' +
|
||||
'undefined values', function () {
|
||||
expect(function () {
|
||||
getColors(arrayOfObjects);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(arrayOfBooleans);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(arrayOfNullValues);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should not throw an error if input is an array of strings, numbers, or' +
|
||||
' undefined values', function () {
|
||||
expect(function () {
|
||||
getColors(arr);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(arrayOfNumbers);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
getColors(arrayOfUndefinedValues);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(typeof getColors).to.be('function');
|
||||
});
|
||||
|
@ -40,11 +101,6 @@ define(function (require) {
|
|||
it('should return the first hex color in the seed colors array', function () {
|
||||
expect(color(arr[0])).to.be(seedColors[0]);
|
||||
});
|
||||
|
||||
// it('should throw a TypeError when the input value is not an array', function () {
|
||||
// console.log(error);
|
||||
// expect(error).to.throwException(typeof str + ' should be an array of strings or numbers');
|
||||
// });
|
||||
});
|
||||
|
||||
describe('Seed Colors', function () {
|
||||
|
@ -66,6 +122,12 @@ define(function (require) {
|
|||
var num1 = 45;
|
||||
var num2 = 72;
|
||||
var num3 = 90;
|
||||
var string = 'Welcome';
|
||||
var bool = true;
|
||||
var nullValue = null;
|
||||
var emptyArr = [];
|
||||
var emptyObject = {};
|
||||
var notAValue;
|
||||
var createColorPalette;
|
||||
var colorPalette;
|
||||
|
||||
|
@ -75,11 +137,38 @@ define(function (require) {
|
|||
|
||||
beforeEach(function () {
|
||||
inject(function (d3, Private) {
|
||||
seedColors = Private(require('components/vislib/components/color/seed_colors'));
|
||||
createColorPalette = Private(require('components/vislib/components/color/color_palette'));
|
||||
colorPalette = createColorPalette(num1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not a number', function () {
|
||||
expect(function () {
|
||||
createColorPalette(string);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createColorPalette(bool);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createColorPalette(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createColorPalette(emptyArr);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createColorPalette(emptyObject);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createColorPalette(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(typeof createColorPalette).to.be('function');
|
||||
});
|
|
@ -157,6 +157,24 @@ define(function (require) {
|
|||
});
|
||||
|
||||
describe('Data array', function () {
|
||||
var childrenObject = {
|
||||
children: []
|
||||
};
|
||||
var seriesObject = {
|
||||
series: []
|
||||
};
|
||||
var rowsObject = {
|
||||
rows: []
|
||||
};
|
||||
var columnsObject = {
|
||||
columns: []
|
||||
};
|
||||
var string = 'string';
|
||||
var number = 23;
|
||||
var boolean = false;
|
||||
var emptyArray = [];
|
||||
var nullValue = null;
|
||||
var notAValue;
|
||||
var dataArray;
|
||||
var testSeries;
|
||||
var testRows;
|
||||
|
@ -175,6 +193,56 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the input is not an object', function () {
|
||||
expect(function () {
|
||||
dataArray(string);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(emptyArray);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should throw an error if property series, rows, or columns is not ' +
|
||||
'present', function () {
|
||||
|
||||
expect(function () {
|
||||
dataArray(childrenObject);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should not throw an error if object has property series, rows, or ' +
|
||||
'columns', function () {
|
||||
|
||||
expect(function () {
|
||||
dataArray(seriesObject);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(rowsObject);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
dataArray(columnsObject);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(typeof dataArray).to.equal('function');
|
||||
});
|
||||
|
@ -214,6 +282,13 @@ define(function (require) {
|
|||
{'label': 'd'},
|
||||
{'label': 'f'}
|
||||
];
|
||||
var string = 'string';
|
||||
var number = 24;
|
||||
var boolean = false;
|
||||
var nullValue = null;
|
||||
var emptyObject = {};
|
||||
var emptyArray = [];
|
||||
var notAValue;
|
||||
var uniq;
|
||||
var testArr;
|
||||
|
||||
|
@ -229,6 +304,38 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an array', function () {
|
||||
expect(function () {
|
||||
uniqLabels(string);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqLabels(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqLabels(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqLabels(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqLabels(emptyObject);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqLabels(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should not throw an error if the input is an array', function () {
|
||||
expect(function () {
|
||||
uniqLabels(emptyArray);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(typeof uniqLabels).to.be('function');
|
||||
});
|
||||
|
@ -244,14 +351,24 @@ define(function (require) {
|
|||
});
|
||||
|
||||
describe('Get series', function () {
|
||||
var string = 'string';
|
||||
var number = 24;
|
||||
var boolean = false;
|
||||
var nullValue = null;
|
||||
var rowsObject = {
|
||||
rows: []
|
||||
};
|
||||
var columnsObject = {
|
||||
columns: []
|
||||
};
|
||||
var emptyObject = {};
|
||||
var emptyArray = [];
|
||||
var notAValue;
|
||||
var getSeries;
|
||||
var columnsLabels;
|
||||
var rowsLabels;
|
||||
var seriesLabels;
|
||||
var columnsArr;
|
||||
var rowsArr;
|
||||
var seriesArr;
|
||||
var error;
|
||||
|
||||
beforeEach(function () {
|
||||
module('GetSeriesUtilService');
|
||||
|
@ -262,13 +379,53 @@ define(function (require) {
|
|||
getSeries = Private(require('components/vislib/components/labels/flatten_series'));
|
||||
columnsLabels = getSeries(columnsData);
|
||||
rowsLabels = getSeries(rowsData);
|
||||
seriesLabels = getSeries(seriesData);
|
||||
columnsArr = _.isArray(columnsLabels);
|
||||
rowsArr = _.isArray(rowsLabels);
|
||||
seriesArr = _.isArray(seriesLabels);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an object', function () {
|
||||
expect(function () {
|
||||
getSeries(string);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getSeries(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getSeries(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getSeries(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getSeries(emptyArray);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
getSeries(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should throw an if property rows or columns is not set on the object', function () {
|
||||
expect(function () {
|
||||
getSeries(emptyObject);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should not throw an error if rows or columns set on object', function () {
|
||||
expect(function () {
|
||||
getSeries(rowsObject);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
getSeries(columnsObject);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(typeof getSeries).to.be('function');
|
||||
});
|
||||
|
@ -281,10 +438,6 @@ define(function (require) {
|
|||
expect(rowsArr).to.be(true);
|
||||
});
|
||||
|
||||
it('should return an empty array if input is data.series', function () {
|
||||
expect(seriesLabels.length).to.be(0);
|
||||
});
|
||||
|
||||
it('should return an array of the same length as as input data.columns', function () {
|
||||
expect(columnsLabels.length).to.be(columnsData.columns.length);
|
||||
});
|
|
@ -16,7 +16,11 @@ define(function (require) {
|
|||
{
|
||||
label: '200',
|
||||
values: [
|
||||
{x: 'v1', y: 234}, {x: 'v2', y: 34}, {x: 'v3', y: 834}, {x: 'v4', y: 1234}, {x: 'v5', y: 4}
|
||||
{x: 'v1', y: 234},
|
||||
{x: 'v2', y: 34},
|
||||
{x: 'v3', y: 834},
|
||||
{x: 'v4', y: 1234},
|
||||
{x: 'v5', y: 4}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -27,13 +31,19 @@ define(function (require) {
|
|||
{
|
||||
label: '200',
|
||||
values: [
|
||||
{x: '1', y: 234}, {x: '2', y: 34}, {x: '3', y: 834}, {x: '4', y: 1234}, {x: '5', y: 4}
|
||||
{x: '1', y: 234},
|
||||
{x: '2', y: 34},
|
||||
{x: '3', y: 834},
|
||||
{x: '4', y: 1234},
|
||||
{x: '5', y: 4}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '404',
|
||||
values: [
|
||||
{x: '1', y: 1234}, {x: '3', y: 234}, {x: '5', y: 34}
|
||||
{x: '1', y: 1234},
|
||||
{x: '3', y: 234},
|
||||
{x: '5', y: 34}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -46,6 +56,25 @@ define(function (require) {
|
|||
};
|
||||
|
||||
var ordered = {};
|
||||
var childrenObject = {
|
||||
children: []
|
||||
};
|
||||
var seriesObject = {
|
||||
series: []
|
||||
};
|
||||
var rowsObject = {
|
||||
rows: []
|
||||
};
|
||||
var columnsObject = {
|
||||
columns: []
|
||||
};
|
||||
var emptyObject = {};
|
||||
var str = 'string';
|
||||
var number = 24;
|
||||
var boolean = false;
|
||||
var nullValue = null;
|
||||
var emptyArray = [];
|
||||
var notAValue;
|
||||
|
||||
describe('Zero Injection (main)', function () {
|
||||
var injectZeros;
|
||||
|
@ -64,6 +93,56 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the input is not an object', function () {
|
||||
expect(function () {
|
||||
injectZeros(str);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(emptyArray);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should throw an error if property series, rows, or columns is not ' +
|
||||
'present', function () {
|
||||
|
||||
expect(function () {
|
||||
injectZeros(childrenObject);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should not throw an error if object has property series, rows, or ' +
|
||||
'columns', function () {
|
||||
|
||||
expect(function () {
|
||||
injectZeros(seriesObject);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(rowsObject);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function () {
|
||||
injectZeros(columnsObject);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(_.isFunction(injectZeros)).to.be(true);
|
||||
});
|
||||
|
@ -118,6 +197,32 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an object', function () {
|
||||
expect(function () {
|
||||
orderXValues(str);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
orderXValues(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
orderXValues(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
orderXValues(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
orderXValues(emptyArray);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
orderXValues(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return a function', function () {
|
||||
expect(_.isFunction(orderXValues)).to.be(true);
|
||||
});
|
||||
|
@ -146,10 +251,36 @@ define(function (require) {
|
|||
beforeEach(function () {
|
||||
inject(function (Private) {
|
||||
uniqueKeys = Private(require('components/vislib/components/zero_injection/uniq_keys'));
|
||||
results = uniqueKeys(multiSeriesData.series);
|
||||
results = uniqueKeys(multiSeriesData);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an object', function () {
|
||||
expect(function () {
|
||||
uniqueKeys(str);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqueKeys(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqueKeys(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqueKeys(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqueKeys(emptyArray);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
uniqueKeys(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return a function', function () {
|
||||
expect(_.isFunction(uniqueKeys)).to.be(true);
|
||||
});
|
||||
|
@ -212,6 +343,32 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an array', function () {
|
||||
expect(function () {
|
||||
createZeroArray(str);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createZeroArray(number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createZeroArray(boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createZeroArray(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createZeroArray(emptyObject);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
createZeroArray(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return a function', function () {
|
||||
expect(_.isFunction(createZeroArray)).to.be(true);
|
||||
});
|
||||
|
@ -275,6 +432,32 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if input are not arrays', function () {
|
||||
expect(function () {
|
||||
zeroFillArray(str, str);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
zeroFillArray(number, number);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
zeroFillArray(boolean, boolean);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
zeroFillArray(nullValue, nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
zeroFillArray(emptyObject, emptyObject);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
zeroFillArray(notAValue, notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should return a function', function () {
|
||||
expect(_.isFunction(zeroFillArray)).to.be(true);
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue