Merge branch 'master' into template_vistype

Conflicts:
	src/kibana/plugins/vis_types/index.js
	test/unit/index.html
This commit is contained in:
Spencer Alger 2014-10-27 12:23:56 -07:00
commit 2aa654a8c1
24 changed files with 754 additions and 83 deletions

View file

@ -857,6 +857,6 @@ require('routes')
# Attribution
This Javascript guide forked from the [node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) and is
This JavaScript guide forked from the [node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) and is
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
license.

View file

@ -0,0 +1,44 @@
define(function (require) {
var _ = require('lodash');
return function (leaf) {
// walk up the branch for each parent
function walk(item, memo) {
// record the the depth
var depth = item.depth - 1;
// Using the aggConfig determin what the field name is. If the aggConfig
// doesn't exist (which means it's an _all agg) then use the level for
// the field name
var col = item.aggConfig;
var field = (col && col.params && col.params.field && col.params.field.name)
|| (col && col.label)
|| ('level ' + item.depth);
// Set the bucket name, and use the converter to format the field if
// the field exists.
var bucket = item.name;
if (col && col.field && col.field.format && col.field.format.convert) {
bucket = col.field.format.convert(bucket);
}
// Add the row to the tooltipScope.rows
memo.unshift({
aggConfig: col,
depth: depth,
field: field,
bucket: bucket,
metric: item.value
});
// If the item has a parent and it's also a child then continue walking
// up the branch
if (item.parent && item.parent.parent) {
return walk(item.parent, memo);
} else {
return memo;
}
}
return walk(leaf, []);
};
});

View file

@ -3,6 +3,7 @@ define(function (require) {
var _ = require('lodash');
var $ = require('jquery');
var $tooltip = $(require('text!plugins/vis_types/tooltips/pie.html'));
var collectBranch = require('components/visualize/_collect_branch');
var $tooltipScope = $rootScope.$new();
$compile($tooltip)($tooltipScope);
@ -18,40 +19,15 @@ define(function (require) {
sum = parent.value;
}
var rows = $tooltipScope.rows = [];
// Collect the current leaf and parents into an array of values
var rows = collectBranch(datum);
// walk up the branch for each parent
(function walk(item) {
// record the the depth
var i = item.depth - 1;
// Using the aggConfig determin what the field name is. If the aggConfig
// doesn't exist (which means it's an _all agg) then use the level for
// the field name
var col = item.aggConfig;
var field = (col && col.params && col.params.field && col.params.field.name)
|| (col && col.label)
|| ('level ' + datum.depth);
// Set the bucket name, and use the converter to format the field if
// the field exists.
var bucket = item.name;
if (col && col.field) bucket = col.field.format.convert(bucket);
// Add the row to the tooltipScope.rows
rows.unshift({
spacer: $sce.trustAsHtml(_.repeat(' ', i)),
field: field,
bucket: bucket,
metric: item.value + ' (' + Math.round((item.value / sum) * 100) + '%)'
});
// If the item has a parent and it's also a child then continue walking
// up the branch
if (item.parent && item.parent.parent) {
walk(item.parent);
}
})(datum);
// Map those values to what the tooltipSource.rows format.
$tooltipScope.rows = _.map(rows, function (row) {
row.spacer = $sce.trustAsHtml(_.repeat(' ', row.depth));
row.metric = row.metric + ' (' + Math.round((row.metric / sum) * 100) + '%)';
return row;
});
$tooltipScope.metricCol = _.find(columns, { categoryName: 'metric' });

View file

@ -2,6 +2,7 @@ define(function (require) {
var _ = require('lodash');
var typeahead = require('modules').get('kibana/typeahead');
require('css!components/typeahead/typeahead.css');
require('components/typeahead/_input');
require('components/typeahead/_items');

View file

@ -1,3 +1,7 @@
@import (reference) "../../styles/_bootstrap.less";
@import (reference) "../../styles/theme/_theme.less";
@import (reference) "../../styles/_variables.less";
.typeahead {
position: relative;

View file

@ -21,16 +21,28 @@ define(function (require) {
return new Data(data, attr);
}
var offset;
if (attr.mode === 'stacked') {
offset = 'zero';
} else if (attr.mode === 'percentage') {
offset = 'expand';
} else if (attr.mode === 'grouped') {
offset = 'group';
} else {
offset = attr.mode;
}
this.data = data;
this._normalizeOrdered();
this._attr = attr;
this._attr = _.defaults(attr || {}, {
offset: 'zero',
// d3 stack function
stack: d3.layout.stack()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.offset(this._attr.offset)
.offset(offset || 'zero')
});
}
@ -83,8 +95,9 @@ define(function (require) {
var seriesLabel;
_.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 rootSeries = obj.series || (obj.slices && obj.slices.children);
var dataLength = rootSeries ? rootSeries.length : 0;
var label = dataLength === 1 ? rootSeries[0].label : undefined;
if (!seriesLabel) {
seriesLabel = label;
@ -168,8 +181,12 @@ define(function (require) {
* @returns {boolean}
*/
Data.prototype.shouldBeStacked = function (series) {
var isHistogram = (this._attr.type === 'histogram');
var isArea = (this._attr.type === 'area');
var isOverlapping = (this._attr.mode === 'overlap');
// Series should be an array
return (this._attr.type === 'histogram' && series.length > 1);
return (isHistogram || isArea && !isOverlapping && series.length > 1);
};
/**
@ -184,9 +201,20 @@ define(function (require) {
Data.prototype.getYMaxValue = function () {
var self = this;
var arr = [];
var grouped = (self._attr.mode === 'grouped');
if (self._attr.mode === 'percentage') {
return 1;
}
if (self._attr.mode === 'percentage') {
return 1;
}
// 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)) {
if (self.shouldBeStacked(series) && !grouped) {
return arr.push(self.getYStackMax(series));
}
return arr.push(self.getYMax(series));

View file

@ -21,9 +21,7 @@ define(function (require) {
this.chartData = chartData;
this.color = type === 'pie' ? handler.data.getPieColorFunc() : handler.data.getColorFunc();
this._attr = _.defaults(handler._attr || {}, {
yValue: function (d) {
return d.y;
},
yValue: function (d) { return d.y; },
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout')
});
}
@ -37,6 +35,7 @@ define(function (require) {
* e: (d3.event|*), handler: (Object|*)}} Event response object
*/
Dispatch.prototype.eventResponse = function (d, i) {
var isPercentage = (this._attr.mode === 'percentage');
var label = d.label;
var getYValue = this._attr.yValue;
var color = this.color;
@ -44,6 +43,18 @@ define(function (require) {
var attr = this._attr;
var handler = this.handler;
if (chartData.series) {
// Find object with the actual d value and add it to the point object
var object = _.find(chartData.series, { 'label': label });
d.value = +object.values[i].y;
if (isPercentage) {
// Add the formatted percentage to the point object
d.percent = (100 * d.y).toFixed(1) + '%';
}
}
return {
value: getYValue(d, i),
point: d,

View file

@ -9,6 +9,7 @@ define(function (require) {
return {
histogram: Private(require('components/vislib/lib/handler/types/column')),
line: Private(require('components/vislib/lib/handler/types/column')),
area: Private(require('components/vislib/lib/handler/types/column')),
pie: Private(require('components/vislib/lib/handler/types/pie'))
};
};

View file

@ -12,6 +12,7 @@ define(function (require) {
return {
histogram: Private(require('components/vislib/lib/layout/types/column_layout')),
line: Private(require('components/vislib/lib/layout/types/column_layout')),
area: Private(require('components/vislib/lib/layout/types/column_layout')),
pie: Private(require('components/vislib/lib/layout/types/pie_layout'))
};
};

View file

@ -30,6 +30,7 @@ define(function (require) {
'blurredOpacity' : 0.3,
'focusOpacity' : 1,
'defaultOpacity' : 1,
'legendDefaultOpacity': 1,
'isOpen' : true
});
}
@ -140,10 +141,22 @@ define(function (require) {
visEl.selectAll(liClass).style('opacity', self._attr.focusOpacity);
})
.on('mouseout', function () {
visEl.selectAll('.color').style('opacity', self._attr.defaultOpacity);
/*
* The default opacity of elements in charts may be modified by the
* chart constructor, and so may differ from that of the legend
*/
visEl.select('.chart')
.selectAll('.color')
.style('opacity', self._attr.defaultOpacity);
// Legend values should always return to their default opacity of 1
visEl.select('.legend-ul')
.selectAll('.color')
.style('opacity', self._attr.legendDefaultOpacity);
});
};
return Legend;
};
});
});

View file

@ -56,6 +56,16 @@ define(function (require) {
*/
YAxis.prototype.getYAxis = function (height) {
var yScale = this.getYScale(height);
var isPercentage = (this._attr.mode === 'percentage');
var tickFormat;
if (isPercentage) {
tickFormat = d3.format('%');
} else if (height <= 1 && !isPercentage) {
tickFormat = d3.format('n');
} else {
tickFormat = d3.format('s');
}
// y scale should never be `NaN`
if (!yScale || _.isNaN(yScale)) {
@ -64,14 +74,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');
if (this.yScale.domain()[1] <= 10) {
this.yAxis.tickFormat(d3.format('n'));
}
.scale(yScale)
.tickFormat(tickFormat)
.ticks(this.tickScale(height))
.orient('left');
return this.yAxis;
};
@ -104,6 +110,8 @@ define(function (require) {
YAxis.prototype.draw = function () {
var self = this;
var margin = this._attr.margin;
var mode = this._attr.mode;
var isWiggleOrSilhouette = (mode === 'wiggle' || mode === 'silhouette');
var div;
var width;
var height;
@ -123,15 +131,18 @@ define(function (require) {
var yAxis = self.getYAxis(height);
// Append svg and y axis
svg = div.append('svg')
.attr('width', width)
.attr('height', height + margin.top + margin.bottom);
// The yAxis should not appear if mode is set to 'wiggle' or 'silhouette'
if (!isWiggleOrSilhouette) {
// Append svg and y axis
svg = div.append('svg')
.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);
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (width - 2) + ',' + margin.top + ')')
.call(yAxis);
}
});
};
};

View file

@ -0,0 +1,370 @@
define(function (require) {
return function AreaChartFactory(d3, Private) {
var _ = require('lodash');
var $ = require('jquery');
var Chart = Private(require('components/vislib/visualizations/_chart'));
var errors = require('errors');
require('css!components/vislib/styles/main');
/**
* Area chart visualization
*
* @class AreaChart
* @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
*/
_(AreaChart).inherits(Chart);
function AreaChart(handler, chartEl, chartData) {
if (!(this instanceof AreaChart)) {
return new AreaChart(handler, chartEl, chartData);
}
AreaChart.Super.apply(this, arguments);
var raw;
var fieldIndex;
if (handler.data.data.raw) {
raw = handler.data.data.raw.columns;
fieldIndex = _.findIndex(raw, {'categoryName': 'group'});
}
this.isOverlapping = (handler._attr.mode === 'overlap');
if (this.isOverlapping) {
// Default opacity should return to 0.6 on mouseout
handler._attr.defaultOpacity = 0.6;
}
this.fieldFormatter = (raw && raw[fieldIndex]) ?
raw[fieldIndex].field.format.convert : function (d) { return d; };
this._attr = _.defaults(handler._attr || {}, {
xValue: function (d) { return d.x; },
yValue: function (d) { return d.y; }
});
}
/**
* Stacks chart data values
* TODO: refactor so that this is called from the data module
*
* @method stackData
* @param data {Object} Elasticsearch query result for this chart
* @returns {Array} Stacked data objects with x, y, and y0 values
*/
AreaChart.prototype.stackData = function (data) {
var self = this;
var stack = this._attr.stack;
return stack(data.series.map(function (d) {
var label = d.label;
return d.values.map(function (e, i) {
return {
label: label,
x: self._attr.xValue.call(d.values, e, i),
y: self._attr.yValue.call(d.values, e, i)
};
});
}));
};
/**
* Adds SVG path to area chart
*
* @method addPath
* @param svg {HTMLElement} SVG to which rect are appended
* @param layers {Array} Chart data array
* @returns {D3.UpdateSelection} SVG with path added
*/
AreaChart.prototype.addPath = function (svg, layers) {
var self = this;
var ordered = this.handler.data.get('ordered');
var isTimeSeries = (ordered && ordered.date);
var isOverlapping = this.isOverlapping;
var color = this.handler.data.getColorFunc();
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
var height = yScale.range()[0];
var defaultOpacity = this._attr.defaultOpacity;
var area = d3.svg.area()
.x(function (d) {
if (isTimeSeries) {
return xScale(d.x);
}
return xScale(d.x) + xScale.rangeBand() / 2;
})
.y0(function (d) {
if (isOverlapping) {
return height;
}
return yScale(d.y0);
})
.y1(function (d) {
if (isOverlapping) {
return yScale(d.y);
}
return yScale(d.y0 + d.y);
});
var layer;
var path;
// Data layers
layer = svg.selectAll('.layer')
.data(layers)
.enter().append('g')
.attr('class', function (d, i) {
return i;
});
// Append path
path = layer.append('path')
.attr('class', function (d) {
return self.colorToClass(color(self.fieldFormatter(d[0].label)));
})
.style('fill', function (d) {
return color(self.fieldFormatter(d[0].label));
})
.style('opacity', defaultOpacity);
// update
path.attr('d', function (d) {
return area(d);
});
return path;
};
/**
* Adds Events to SVG circles
*
* @method addCircleEvents
* @param circles {D3.UpdateSelection} SVG circles
* @returns {HTMLElement} circles with event listeners attached
*/
AreaChart.prototype.addCircleEvents = function (circles) {
var events = this.events;
var dispatch = this.events._attr.dispatch;
circles
.on('mouseover.circle', function mouseOverCircle(d, i) {
var circle = this;
d3.select(circle)
.classed('hover', true)
.style('stroke', '#333')
.style('cursor', 'pointer')
.style('opacity', 1);
dispatch.hover(events.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('click.circle', function clickCircle(d, i) {
dispatch.click(events.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('mouseout.circle', function mouseOutCircle() {
var circle = this;
d3.select(circle)
.classed('hover', false)
.style('stroke', null)
.style('opacity', 0);
});
};
/**
* Adds SVG circles to area chart
*
* @method addCircles
* @param svg {HTMLElement} SVG to which circles are appended
* @param data {Array} Chart data array
* @returns {D3.UpdateSelection} SVG with circles added
*/
AreaChart.prototype.addCircles = function (svg, data) {
var self = this;
var color = this.handler.data.getColorFunc();
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
var ordered = this.handler.data.get('ordered');
var circleRadius = 4;
var circleStrokeWidth = 1;
var tooltip = this.tooltip;
var isTooltip = this._attr.addTooltip;
var isOverlapping = this.isOverlapping;
var layer;
var circles;
layer = svg.selectAll('.points')
.data(data)
.enter()
.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')
.attr('class', function circleClass(d) {
return d.label;
})
.attr('fill', function (d) {
return color(self.fieldFormatter(d.label));
})
.attr('stroke', function strokeColor(d) {
return color(self.fieldFormatter(d.label));
})
.attr('stroke-width', circleStrokeWidth);
// update
circles
.attr('cx', function cx(d) {
if (ordered && ordered.date) {
return xScale(d.x);
}
return xScale(d.x) + xScale.rangeBand() / 2;
})
.attr('cy', function cy(d) {
if (isOverlapping) {
return yScale(d.y);
}
return yScale(d.y0 + d.y);
})
.attr('r', circleRadius)
.style('opacity', 0);
// Add tooltip
if (isTooltip) {
circles.call(tooltip.render());
}
return circles;
};
/**
* Adds SVG clipPath
*
* @method addClipPath
* @param svg {HTMLElement} SVG to which clipPath is appended
* @param width {Number} SVG width
* @param height {Number} SVG height
* @returns {D3.UpdateSelection} SVG with clipPath added
*/
AreaChart.prototype.addClipPath = function (svg, width, height) {
// Prevents circles from being clipped at the top of the chart
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)
.append('rect')
.attr('x', startX)
.attr('y', startY)
.attr('width', width)
// Adding clipPathBuffer to height so it doesn't
// cutoff the lower part of the chart
.attr('height', height + clipPathBuffer);
};
/**
* Renders d3 visualization
*
* @method draw
* @returns {Function} Creates the area chart
*/
AreaChart.prototype.draw = function () {
// Attributes
var self = this;
var xScale = this.handler.xAxis.xScale;
var $elem = $(this.chartEl);
var margin = this._attr.margin;
var elWidth = this._attr.width = $elem.width();
var elHeight = this._attr.height = $elem.height();
var minWidth = 20;
var minHeight = 20;
var div;
var svg;
var width;
var height;
var layers;
var circles;
var path;
return function (selection) {
selection.each(function (data) {
// Stack data
layers = self.stackData(data);
// Get the width and height
width = elWidth;
height = elHeight - margin.top - margin.bottom;
if (width < minWidth || height < minHeight) {
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 + ')');
// add clipPath to hide circles when they go out of bounds
self.addClipPath(svg, width, height);
// addBrush canvas
self.events.addBrush(xScale, svg);
// add path
path = self.addPath(svg, layers);
// 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', 0)
.attr('y1', height)
.attr('x2', width)
.attr('y2', height)
.style('stroke', '#ddd')
.style('stroke-width', 1);
return svg;
});
};
};
return AreaChart;
};
});

View file

@ -76,10 +76,7 @@ define(function (require) {
*/
ColumnChart.prototype.addBars = function (svg, layers) {
var self = this;
var data = this.chartData;
var color = this.handler.data.getColorFunc();
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
var tooltip = this.tooltip;
var isTooltip = this._attr.addTooltip;
var layer;
@ -104,13 +101,53 @@ define(function (require) {
bars
.enter()
.append('rect')
.attr('class', function (d) {
return self.colorToClass(color(self.fieldFormatter(d.label)));
})
.attr('fill', function (d) {
return color(self.fieldFormatter(d.label));
});
.attr('class', function (d) {
return self.colorToClass(color(self.fieldFormatter(d.label)));
})
.attr('fill', function (d) {
return color(self.fieldFormatter(d.label));
});
self.updateBars(bars);
// Add tooltip
if (isTooltip) {
bars.call(tooltip.render());
}
return bars;
};
/**
* Determines whether bars are grouped or stacked and updates the D3
* selection
*
* @method updateBars
* @param bars {D3.UpdateSelection} SVG with rect added
* @returns {D3.UpdateSelection}
*/
ColumnChart.prototype.updateBars = function (bars) {
var offset = this._attr.mode;
if (offset === 'grouped') {
return this.addGroupedBars(bars);
}
return this.addStackedBars(bars);
};
/**
* Adds stacked bars to column chart visualization
*
* @method addStackedBars
* @param bars {D3.UpdateSelection} SVG with rect added
* @returns {D3.UpdateSelection}
*/
ColumnChart.prototype.addStackedBars = function (bars) {
var data = this.chartData;
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
// update
bars
.attr('x', function (d) {
return xScale(d.x);
@ -134,9 +171,57 @@ define(function (require) {
return yScale(d.y0) - yScale(d.y0 + d.y);
});
if (isTooltip) {
bars.call(tooltip.render());
}
return bars;
};
/**
* Adds grouped bars to column chart visualization
*
* @method addGroupedBars
* @param bars {D3.UpdateSelection} SVG with rect added
* @returns {D3.UpdateSelection}
*/
ColumnChart.prototype.addGroupedBars = function (bars) {
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
var data = this.chartData;
var n = data.series.length;
var height = yScale.range()[0];
var groupSpacingPercentage = 0.15;
var isTimeScale = (data.ordered && data.ordered.date);
var minWidth = 1;
var barWidth;
// update
bars
.attr('x', function (d, i, j) {
if (isTimeScale) {
var groupWidth = xScale(data.ordered.min + data.ordered.interval) -
xScale(data.ordered.min);
var groupSpacing = groupWidth * groupSpacingPercentage;
barWidth = (groupWidth - groupSpacing) / n;
return xScale(d.x) + barWidth * j;
}
return xScale(d.x) + xScale.rangeBand() / n * j;
})
.attr('width', function () {
if (barWidth < minWidth) {
throw new errors.ContainerTooSmall();
}
if (isTimeScale) {
return barWidth;
}
return xScale.rangeBand() / n;
})
.attr('y', function (d) {
return yScale(d.y);
})
.attr('height', function (d) {
return height - yScale(d.y);
});
return bars;
};

View file

@ -11,8 +11,9 @@ define(function (require) {
*/
return {
histogram: Private(require('components/vislib/visualizations/column_chart')),
pie: Private(require('components/vislib/visualizations/pie_chart')),
line: Private(require('components/vislib/visualizations/line_chart'))
line: Private(require('components/vislib/visualizations/line_chart')),
area: Private(require('components/vislib/visualizations/area_chart')),
pie: Private(require('components/vislib/visualizations/pie_chart'))
};
};

View file

@ -0,0 +1,50 @@
define(function (require) {
return function HistogramVisType(Private) {
var VisType = Private(require('plugins/vis_types/_vis_type'));
var Schemas = Private(require('plugins/vis_types/_schemas'));
return new VisType({
name: 'area',
title: 'Area chart',
icon: 'fa-area-chart',
vislibParams: {
shareYAxis: true,
addTooltip: true,
addLegend: true,
},
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Y-Axis',
min: 1,
max: 1,
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
title: 'X-Axis',
min: 0,
max: 1
},
{
group: 'buckets',
name: 'group',
title: 'Split Area',
min: 0,
max: 1
},
{
group: 'buckets',
name: 'split',
title: 'Split Chart',
min: 0,
max: 1
}
])
});
};
});

View file

@ -4,4 +4,5 @@ define(function (require) {
visTypes.register(require('plugins/vis_types/vislib/histogram'));
visTypes.register(require('plugins/vis_types/vislib/line'));
visTypes.register(require('plugins/vis_types/vislib/pie'));
visTypes.register(require('plugins/vis_types/area'));
});

View file

@ -2,7 +2,10 @@
<tbody>
<tr ng-repeat="detail in details" >
<td><b>{{detail.label}}</b></td>
<td>{{detail.value}}</td>
<td>
{{detail.value}}
<span ng-if="detail.percent"> ({{detail.percent}})</span>
</td>
</tr>
</tbody>
</table>

View file

@ -97,6 +97,7 @@ define(function (require) {
var label;
var val;
var percent;
switch (col) {
case colX:
@ -105,7 +106,8 @@ define(function (require) {
break;
case colY:
label = 'y';
val = datum.y;
val = datum.value;
percent = datum.percent;
break;
case colColor:
label = 'color';
@ -118,7 +120,8 @@ define(function (require) {
return {
label: label,
value: val
value: val,
percent: percent
};
});

View file

@ -174,7 +174,6 @@ notifications {
@import "./_table.less";
@import "./_notify.less";
@import "./_typeahead.less";
//== Nav tweaks
.nav-condensed > li > a {

View file

@ -0,0 +1,14 @@
# This monkeypatch is needed to ensure the X-Frame-Options header is
# never set by rack-protection.
#
# http://stackoverflow.com/a/19232793/296172
#
module Rack
module Protection
class FrameOptions < Base
def call(env)
@app.call(env)
end
end
end
end

View file

@ -9,6 +9,7 @@ require "lib/ColorLogger"
require "routes/home"
require "sinatra/json"
require "routes/proxy"
require "lib/FrameOptions"
require "routes/plugins"
class Logger

View file

@ -148,6 +148,7 @@
'specs/components/agg_response/hierarchical/_transform_aggregation',
'specs/components/agg_response/hierarchical/_create_raw_data',
'specs/components/agg_response/hierarchical/_array_to_linked_list',
'specs/components/agg_response/hierarchical/_collect_branch',
'specs/components/agg_response/tabify/tabify_agg_response'
], function (kibana, sinon) {
kibana.load(function () {

View file

@ -0,0 +1,56 @@
define(function (require) {
var collectBranch = require('components/visualize/_collect_branch');
describe('collectBranch()', function () {
var results;
var convert = function (name) {
return 'converted:' + name;
};
beforeEach(function () {
results = collectBranch({
name: 'bucket3',
depth: 3,
value: 6,
field: { format: { convert: convert } },
aggConfig: { params: { field: { name: 'field3' } } },
parent: {
name: 'bucket2',
depth: 2,
value: 12,
aggConfig: { label: 'field2' },
parent: {
name: 'bucket1',
depth: 1,
value: 24,
parent: {}
}
}
});
});
it('should return an array with bucket objects', function () {
expect(results).to.be.an(Array);
expect(results).to.have.length(3);
expect(results[0]).to.have.property('metric', 24);
expect(results[0]).to.have.property('depth', 0);
expect(results[0]).to.have.property('bucket', 'bucket1');
expect(results[0]).to.have.property('field', 'level 1');
expect(results[0]).to.have.property('aggConfig');
expect(results[1]).to.have.property('metric', 12);
expect(results[1]).to.have.property('depth', 1);
expect(results[1]).to.have.property('bucket', 'bucket2');
expect(results[1]).to.have.property('field', 'field2');
expect(results[1]).to.have.property('aggConfig');
expect(results[2]).to.have.property('metric', 6);
expect(results[2]).to.have.property('depth', 2);
expect(results[2]).to.have.property('bucket', 'bucket3');
expect(results[2]).to.have.property('field', 'field3');
expect(results[2]).to.have.property('aggConfig');
});
});
});

View file

@ -1,3 +0,0 @@
define(function (require) {
console.log('loading other plugin');
});