Merge remote-tracking branch 'upstream/master' into feature/zeroclip

This commit is contained in:
Rashid Khan 2014-10-17 15:39:21 -07:00
commit d2c0535beb
65 changed files with 1709 additions and 719 deletions

View file

@ -530,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_') {

View file

@ -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];

View file

@ -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;
};
};

View file

@ -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',

View file

@ -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;
};
};

View file

@ -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();
};
};
});

View file

@ -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);
};
};
});

View file

@ -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();
};
};
});

View file

@ -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');
};
};

View file

@ -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;
};
};

View file

@ -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];

View file

@ -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;
};
};

View file

@ -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;
};
};

View file

@ -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;
};
};

View file

@ -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',

View file

@ -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;

View file

@ -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);
});
};
};

View file

@ -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));
});
};
};

View file

@ -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)) {
@ -19,7 +23,6 @@ define(function (require) {
this.data = data;
this._attr = attr;
// d3 stack function
this._attr = _.defaults(attr || {}, {
offset: 'zero',
stack: d3.layout.stack()
@ -29,7 +32,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 +47,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 +67,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 +82,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 +100,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 +113,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 +134,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 +190,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 +219,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 +234,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 +265,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 +282,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,27 +313,54 @@ 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());
};

View file

@ -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;

View file

@ -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;

View file

@ -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')),

View file

@ -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;
};
};
});

View file

@ -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;
};
};
});

View file

@ -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();
};

View file

@ -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')),

View file

@ -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;

View file

@ -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();
});
};

View file

@ -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');
});

View file

@ -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');
});

View file

@ -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;

View file

@ -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();
});
};

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
});
};
};

View file

@ -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];
};

View file

@ -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);
};

View file

@ -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)

View file

@ -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)

View file

@ -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);
}

View file

@ -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')),

View file

@ -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"
# Applications loaded and included into Kibana. Use the settings below to
# customize the applications and thier names.

View file

@ -111,26 +111,26 @@
'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',

View file

@ -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');
});

View file

@ -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);
});

View file

@ -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);
});