Merge branch 'master' into truncate_by_height

Conflicts:
	src/kibana/components/config/defaults.js
This commit is contained in:
Spencer Alger 2014-09-25 08:34:58 -07:00
commit aec35efd33
69 changed files with 1451 additions and 386 deletions

View file

@ -443,6 +443,10 @@
- Now that all calls to _data and _removeData have been replaced
- **[src/kibana/components/index_patterns/_mapper.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/index_patterns/_mapper.js)**
- Change index to be the resolved in some way, last three months, last hour, last year, whatever
- **[src/kibana/components/vis_types/converters/pie.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vis_types/converters/pie.js)**
- refactor this code to simplify and possibly merge with data converter code below
- **[src/kibana/components/vislib/lib/data.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/lib/data.js)**
- need to make this more generic
- **[src/kibana/components/vislib/vis.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/vis.js)**
- need to come up with a solution for resizing when no data is available
- **[src/kibana/components/vislib/visualizations/column_chart.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/visualizations/column_chart.js)**

View file

@ -43,7 +43,7 @@
<div ng-show="!state.panels.length" class="text-center start-screen">
<h2>Ready to get started?</h2>
<p>Click the <i class="fa fa-plus" ng-click="openAdd()"></i> button in the menu bar above to add a visualization to the dashboard. <br/>If you haven't setup a visualization yet visit the <a href="#/visualize">"Visualize"</a> tab to create your first visualization.</p>
<p>Click the <a class="btn btn-xs navbtn-inverse" ng-click="openAdd()"><i class="fa fa-plus"></i></a> button in the menu bar above to add a visualization to the dashboard. <br/>If you haven't setup a visualization yet visit the <a href="#/visualize">"Visualize"</a> tab to create your first visualization.</p>
</div>
<dashboard-grid></dashboard-grid>

View file

@ -55,7 +55,7 @@ define(function (require) {
var stateDefaults = {
title: dash.title,
panels: dash.panelsJSON ? JSON.parse(dash.panelsJSON) : [],
query: ''
query: {query_string: {query: '*'}}
};
var $state = $scope.state = appStateFactory.create(stateDefaults);
@ -95,7 +95,7 @@ define(function (require) {
}
$scope.newDashboard = function () {
$location.url('/dashboard');
kbnUrl.change('/dashboard', {}, true);
};
$scope.filterResults = function () {

View file

@ -19,12 +19,6 @@ dashboard-grid {
margin: 20px;
background-color: @gray-lighter;
padding: 20px;
i {
padding: 4px 8px;
background-color: #3d636b;
color: white;
}
}
.gridster {

View file

@ -38,12 +38,12 @@
<div ng-show="field.indexed && !field.analyzed"
ng-click="runAgg(field)"
tooltip="Doc_values are not enabled on this field. This operation may be dangerous."
tooltip="Doc_values are not enabled on this field. This may lead to excess heap consumption"
class="sidebar-item-button warning">Visualize</div>
<div ng-show="field.indexed && field.analyzed"
ng-click="runAgg(field)"
tooltip="Analyzed fields may require more memory to visualize. This operation may be dangerous."
tooltip="Analyzed fields may require more memory to visualize and may not give you the results you expect."
class="sidebar-item-button danger">Visualize</div>
<div ng-show="!field.indexed"

View file

@ -45,7 +45,7 @@ define(function (require) {
}
});
app.controller('discover', function ($scope, config, courier, $route, $window, savedSearches, savedVisualizations,
app.controller('discover', function ($scope, config, courier, $route, $window, $q, savedSearches, savedVisualizations,
Notifier, $location, globalState, appStateFactory, timefilter, Promise, Private, kbnUrl) {
var Vis = Private(require('components/vis/vis'));
@ -57,6 +57,8 @@ define(function (require) {
location: 'Discover'
});
$scope.timefilter = timefilter;
// the saved savedSearch
var savedSearch = $route.current.locals.savedSearch;
$scope.$on('$destroy', savedSearch.destroy);
@ -209,11 +211,17 @@ define(function (require) {
notify.error('An error occured with your request. Reset your inputs and try again.');
}).catch(notify.fatal);
return setupVisualization().then(function () {
$scope.updateTime();
if ($scope.opts.timefield) {
setupVisualization().then(function () {
$scope.updateTime();
init.complete = true;
$scope.$emit('application.load');
});
} else {
init.complete = true;
$scope.$emit('application.load');
});
}
});
});
@ -582,6 +590,8 @@ define(function (require) {
var loadingVis;
var setupVisualization = function () {
// If we're not setting anything up we need to return an empty promise
if (!$scope.opts.timefield) return $q.when();
if (loadingVis) return loadingVis;

View file

@ -51,12 +51,45 @@
<!-- no results -->
<div class="col-md-10" ng-show="resultState === 'none'">
<div class="discover-overlay">
<h2>No results found <i class="fa fa-meh-o"></i></h2>
<div class="col-md-12">
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place. Try expanding your search, or increasing your time window if searching by time. I really want to make this work for you. Help me, help you.
<div class="col-md-10 col-md-offset-1">
<h1>No results found <i class="fa fa-meh-o"></i></h1>
<p>
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place and frankly, I just couldn't find anything good. Help me, help you. Here's some ideas:
</p>
<div ng-show="opts.timefield">
<p>
<h3>Expand your time range</h3>
<p>I see you are looking at an index with a date field. It is possible your query does not match anything in the current time range, or that there is not data at all in the currently selected time range. Click the button below to open the time picker. For future reference you can open the time picker by clicking the <a class="btn btn-xs navbtn" ng-click="toggleTimepicker()">time picker <i class="fa fa-clock-o"></i></a> in the top right corner of your screen.
</p>
</div>
<h3>Refine your query</h3>
<p>
The search bar at the top allows Kibana uses Elasticsearch's support for Lucene Query String syntax. Let's say we're searching web server logs that have been parsed into a few fields.
</p>
<p>
<h4>Examples:</h4>
Find requests that contain the number 200, in any field:
<pre>200</pre>
Or we can search in a specific field. Find 200 in the status field:
<pre>status:200</pre>
Find all status codes between 400-499:
<pre>status:[400 TO 499]</pre>
Find status codes 400-499 with the extension php:
<pre>status:[400 TO 499] AND extension:PHP</pre>
Or HTML
<pre>status:[400 TO 499] AND (extension:php OR extension:html)</pre>
</p>
</div>
</div>
</div>
<!-- loading -->
@ -74,7 +107,7 @@
{{ opts.savedSearch.title }}
<i tooltip="Reload saved query" ng-click="resetQuery();" class="fa fa-undo smallest small"></i>
</h3>
<div class="discover-timechart" >
<div class="discover-timechart" ng-if="opts.timefield">
<center class="small">
<span tooltip="To change the time, click the clock icon in the navigation bar">{{timeRange.from | moment}} - {{timeRange.to | moment}}</span>
<!-- TODO: Currently no way to apply this setting to the visualization -->

View file

@ -19,7 +19,10 @@
</thead>
<tbody>
<tr ng-repeat="conf in configs | filter:advancedFilter" ng-class="conf.value === undefined ? 'default' : 'custom'">
<td class="name">{{conf.name}}</td>
<td class="name">
<b>{{conf.name}}</b><br>
<span class="smaller">{{conf.description}}</span>
</td>
<td class="value">
<form
ng-if="conf.editting"

View file

@ -49,10 +49,11 @@ define(function (require) {
.catch(notify.fatal);
};
$scope.configs = _.map(configDefaults, function (defVal, name) {
$scope.configs = _.map(configDefaults, function (def, name) {
var conf = {
name: name,
defVal: defVal,
defVal: def.value,
description: def.description,
value: configVals[name]
};

View file

@ -1,11 +1,9 @@
<kbn-settings-app section="objects">
<kbn-settings-objects class="container">
<h2>Edit Saved Objects</h2>
<div class="bs-callout bs-callout-warning">
<h4>Caution: You can break stuff here</h4>
Be careful in here, these setting are for very advanced users only. Tweaks you make here can break large poritions of Kibana.
Some of these settings may be undocumented, unsupported or experimental. Seriously, if you thought the "Advance" tab was dangerous this is even more extreme!
</div>
<p>
From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen.
</p>
<form>
<input ng-model="advancedFilter" class="form-control span12" type="text" placeholder="Filter"/>
</form>

View file

@ -6,9 +6,8 @@
</div>
<h1>Edit {{ title }} Object</h1>
<div class="bs-callout bs-callout-warning">
<h4>Caution: You can break stuff here</h4>
Be careful in here, these setting are for very advanced users only. Tweaks you make here can break large poritions of Kibana.
Some of these settings may be undocumented, unsupported or experimental. Seriously, if you thought the "Advance" tab was dangerous this is even more extreme!
<h4>Proceed with caution</h4>
Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn't be.
</div>
</div>
<form name="objectForm" ng-submit="submit()">

View file

@ -24,13 +24,13 @@
<div class="input-group"
ng-class="queryInput.$invalid ? 'has-error' : ''">
<input
ng-model="state.query"
query-input="savedVis.searchSource"
input-focus
kbn-typeahead-input
placeholder="Search..."
type="text"
class="form-control"
ng-model="state.query">
class="form-control">
<button
class="btn btn-default" type="submit"

View file

@ -41,7 +41,7 @@ define(function (require) {
'kibana/notify',
'kibana/courier'
])
.controller('VisEditor', function ($scope, $route, timefilter, appStateFactory, $location, kbnUrl, $timeout) {
.controller('VisEditor', function ($scope, $route, timefilter, appStateFactory, $location, kbnUrl, $timeout, courier) {
var _ = require('lodash');
var angular = require('angular');
@ -98,6 +98,7 @@ define(function (require) {
delete $state.query;
} else {
$state.query = $state.query || searchSource.get('query');
courier.setRootSearchSource(searchSource);
searchSource.set('query', $state.query);
}
@ -139,11 +140,13 @@ define(function (require) {
}
$scope.fetch = function () {
$state.save();
if (!$scope.linked) searchSource.set('query', $state.query);
searchSource.fetch();
};
$scope.startOver = function () {
$location.url('/visualize');
kbnUrl.change('/visualize', {}, true);
};
$scope.doSave = function () {

View file

@ -62,12 +62,6 @@
> span {
width: @vis-editor-nesting-width;
background-color: @brand-success;
.transition(width .3s ease-out);
&.expand {
width: @vis-editor-nesting-expand-width;
}
}
}

View file

@ -22,29 +22,6 @@ define(function (require) {
};
}());
var allIndicators = [];
allIndicators.expanded = false;
allIndicators.expand = toggler(true);
allIndicators.contract = toggler(false, 150);
function toggler(on, delay) {
var all = allIndicators;
var work = function () {
if (delay && all.expanded !== on) return;
all.forEach(function ($scope) {
if (!$scope.bars) return;
$scope.bars.forEach(function ($el) {
$el.toggleClass('expand', on);
});
});
};
return function () {
all.expanded = on;
if (!delay) work();
else setTimeout(work, delay);
};
}
return {
restrict: 'E',
scope: {
@ -53,16 +30,6 @@ define(function (require) {
},
link: function ($scope, $el, attr) {
allIndicators.push($scope);
$el.on('mouseenter', allIndicators.expand);
$el.on('mouseleave', allIndicators.contract);
$scope.$on('$destroy', function () {
_.pull(allIndicators, $scope);
$el.off('mouseenter', allIndicators.expand);
$el.off('mouseleave', allIndicators.contract);
});
$scope.$watchCollection('list', function () {
if (!$scope.list || !$scope.item) return;

View file

@ -8,7 +8,7 @@
ng-repeat="type in visTypes"
ng-href="{{ visTypeUrl(type) }}">
<li>
<i ng-class="type.icon"></i>{{type.name}}
<i ng-class="type.icon"></i>{{type.title}}
</li>
</a>
</ul>

View file

@ -75,7 +75,7 @@ define(function (require) {
config.get = function (key, defaultVal) {
if (vals[key] == null) {
if (defaultVal == null) {
return defaults[key];
return defaults[key].value;
} else {
return _.cloneDeep(defaultVal);
}

View file

@ -1,25 +1,54 @@
define(function (require) {
var _ = require('lodash');
return _.flattenWith('.', {
dateFormat: 'MMMM Do YYYY, HH:mm:ss.SSS',
defaultIndex: null,
refreshInterval: 10000,
metaFields: ['_source', '_id', '_type', '_index'],
'discover:sampleSize': 500,
'fields:popularLimit': 10,
'truncate:maxHeight': 100,
'histogram:barTarget': 50,
'histogram:maxBars': 100,
'csv:separator': ',',
'csv:quoteValues': true,
'history:limit': 10,
'shortDots:enable': false
});
return {
'dateFormat': {
value: 'MMMM Do YYYY, HH:mm:ss.SSS',
description: 'When displaying a pretty formatted date, use this format',
},
'defaultIndex': {
value: null,
description: 'The index to access if no index is set',
},
'metaFields': {
value: ['_source', '_id', '_type', '_index'],
description: 'Fields that exist outside of _source to merge into our document when displaying it',
},
'discover:sampleSize': {
value: 500,
description: 'The number of rows to show in the table',
},
'fields:popularLimit': {
value: 10,
description: 'The top N most popular fields to show',
},
'histogram:barTarget': {
value: 50,
description: 'Attempt to generate around this many bar when using "auto" interval in date histograms',
},
'histogram:maxBars': {
value: 100,
description: 'Never show more than this many bar in date histograms, scale values if needed',
},
'csv:separator': {
value: ',',
description: 'Seperate exported values with this string',
},
'csv:quoteValues': {
value: true,
description: 'Should values be quoted in csv exports?',
},
'history:limit': {
value: 10,
description: 'In fields that have history (eg query inputs), show this many recent values',
},
'shortDots:enable': {
value: false,
description: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz',
},
'truncate:maxHeight': {
value: 100,
description: 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.'
}
};
});

View file

@ -34,7 +34,15 @@ define(function (require) {
*/
function setAppSource(source) {
appSource = source;
appSource.inherits(globalSource);
// walk the parent chain until we get to the global source or nothing
// that's where we will attach to the globalSource
var literalRoot = source;
while (literalRoot._parent && literalRoot._parent !== globalSource) {
literalRoot = literalRoot._parent;
}
literalRoot.inherits(globalSource);
}
/**

View file

@ -5,7 +5,7 @@
<span>"{{ filter.value }}"</span>
</div>
<div class="filter-actions">
<a class="filter-toggle" ng-click="toggleFilter(filter)">toggle</a>
<a class="filter-toggle" ng-click="toggleFilter(filter)"><span ng-show="filter.disabled">enable</span><span ng-hide="filter.disabled">disable</span></a>
<a class="filter-remove" ng-click="removeFilter(filter)">remove</a>
</div>
</div>

View file

@ -7,7 +7,7 @@ define(function (require) {
return function (chart, columns, rows) {
// index of color
var iColor = _.findIndex(columns, { categoryName: 'group' });
var hasColor = iColor !== -1;
var colColor = columns[iColor];
/*****
* Get values related to the X-Axis
@ -79,20 +79,22 @@ define(function (require) {
// setup the formatter for the label
chart.tooltipFormatter = function (datapoint) {
var datum = _.clone(datapoint);
chart.tooltipFormatter = function (datum) {
var vals = [['x', colX], ['y', colY]]
.map(function (set) {
var axis = set[0];
var col = set[1];
var val = datum[axis];
if (colX.field) datum.x = colX.field.format.convert(datum.x);
if (colY.field) datum.y = colY.field.format.convert(datum.y);
var name = (col.field && col.field.name) || col.label || axis;
if (col.field) val = col.field.format.convert(val);
if (colX.metricScaleText) {
datum.y += ' per ' + colX.metricScaleText;
}
return name + ': ' + val;
}).join('<br>');
var out = datum.label ? datum.label + '\n' : '';
out += datum.x + '\n';
out += datum.y;
var out = '';
if (datum.label) out += colColor.field.name + ': ' + datum.label + '<br>';
out += vals;
return out;
};
@ -101,12 +103,12 @@ define(function (require) {
var seriesByLabel = {};
rows.forEach(function (row) {
var seriesLabel = hasColor && row[iColor];
var s = hasColor ? seriesByLabel[seriesLabel] : series[0];
var seriesLabel = colColor && row[iColor];
var s = colColor ? seriesByLabel[seriesLabel] : series[0];
if (!s) {
// I know this could be simplified but I wanted to keep the key order
if (hasColor) {
if (colColor) {
s = {
label: seriesLabel,
values: []
@ -125,6 +127,9 @@ define(function (require) {
y: row[iY === -1 ? row.length - 1 : iY] // y-axis value
};
// skip this datum
if (datum.y == null) return;
if (colX.metricScale) {
// support scaling response values to represent an average value on the y-axis
datum.y = datum.y * colX.metricScale;

View file

@ -0,0 +1,145 @@
define(function (require) {
return function HistogramConverterFn(Private, timefilter) {
var _ = require('lodash');
var moment = require('moment');
var interval = require('utils/interval');
return function (chart, columns, rows) {
// Checks for obj.parent.name and
// Returns an array of parent names
// if they exist.
function checkForParentName(datum) {
var parentNames = [];
parentNames.push(datum.name);
if (datum.parent.name) {
_.forEach(checkForParentName(datum.parent), function (name) {
return parentNames.push(name);
});
}
return parentNames;
}
// tooltip formatter for pie charts
chart.tooltipFormatter = function (datum) {
function sumValue(sum, cur) {
return sum + cur.value;
}
// find the root datum
var root = datum;
while (root.parent) root = root.parent;
// the value of the root datum is the sum of every row. coincidental? not certain
var sum = root.value;
var labels = [];
for (var cur = datum; cur.parent; cur = cur.parent) {
var label = cur.name + ': ' + cur.value;
label += ' (' + Math.round((cur.value / sum) * 100) + '%)';
if (cur === datum) {
label = '<b>' + label + '</b>';
}
labels.unshift(label);
}
return labels.join('<br>');
};
// TODO: refactor this code to simplify and possibly merge with data converter code below
// Creates a collection of all the labels
// and their index value
var sliceLabels = {};
rows.forEach(function (row, i) {
var startIndex = 0;
var stopIndex = row.length - 1;
row = row.slice(startIndex, stopIndex);
// if no label available, return _all
if (row.length === 0) {
return sliceLabels['_all'] = i;
}
row.forEach(function (name, i) {
if (!sliceLabels[name]) {
return sliceLabels[name] = i;
}
});
});
// An array of all the labels sorted by their index number
chart.names = _(sliceLabels)
.pairs()
.sortBy(function (d) {
return d[1];
})
.pluck(function (d) {
return d[0];
})
.value();
// Pie Data Converter
var slices = chart.slices = {};
var children = slices.children = [];
// appends new object (slice) to children array
function appendSlice(array, name) {
return array.push({
name: name,
children: []
});
}
rows.forEach(function (row) {
var rowLength = row.length;
// Name is always the second to last value in the array
// Size is always the last value in the array
var iName = rowLength - 2;
var iSize = rowLength - 1;
// Wrap up the name and size values into an object
var datum = {
name: (row[iName] == null && rowLength >= 2) ? row[iName - 1] : rowLength < 2 ? '_all' : row[iName],
size: row[iSize]
};
// Create an array of the labels (names) that should append a slice
// i.e. { names: '', children: [] }
var startIndex = 0;
var stopIndex = rowLength - 2;
var names = (row[iName] == null) ? row.slice(startIndex, stopIndex - 1) : row.slice(startIndex, stopIndex);
// Keep track of the current children array
var currentArray = children;
// For each name in the names array, append an empty slice if the
// named object is not present, else return
names.forEach(function (name) {
// Find the index of the name in the current children array
var currentNameIndex = _.findIndex(currentArray, { name: name });
// If not present, append an empty slice
if (currentNameIndex === -1) {
appendSlice(currentArray, name);
}
// Update the current array
currentNameIndex = _.findIndex(currentArray, { name: name });
currentArray = currentArray[currentNameIndex].children;
});
// Append datum
currentArray.push(datum);
});
};
};
});

View file

@ -5,12 +5,14 @@ define(function (require) {
return new VisType({
name: 'histogram',
title: 'Vertical bar chart',
icon: 'icon-chart-bar',
vislibParams: {
shareYAxis: true,
addTooltip: true,
addLegend: true,
addBrushing: false,
addEvents: true,
addBrushing: true
},
schemas: new Schemas([
{

View file

@ -7,8 +7,8 @@ define(function (require) {
index: ['name'],
initialSet: [
Private(require('components/vis_types/histogram')),
// Private(require('components/vis_types/line')),
// Private(require('components/vis_types/pie'))
Private(require('components/vis_types/line')),
Private(require('components/vis_types/pie'))
]
});
};

View file

@ -5,6 +5,7 @@ define(function (require) {
return new VisType({
name: 'line',
title: 'Line chart',
icon: 'icon-chart-bar',
vislibParams: {
shareYAxis: true,
@ -17,7 +18,10 @@ define(function (require) {
name: 'metric',
title: 'Y-Axis',
min: 1,
max: 1
max: 1,
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',

View file

@ -2,35 +2,42 @@ define(function (require) {
return function HistogramVisType(Private) {
var VisType = Private(require('components/vis_types/_vis_type'));
var Schemas = Private(require('components/vis_types/_schemas'));
var PieConverter = Private(require('components/vis_types/converters/pie'));
return new VisType({
name: 'pie',
title: 'Pie chart',
icon: 'icon-chart-bar',
vislibParams: {
addEvents: true,
addTooltip: true,
addLegend: true
},
responseConverter: PieConverter,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Slice Size',
min: 1,
max: 1
max: 1,
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-scissors',
title: 'Slices',
title: 'Create/Split Slices',
min: 0,
max: 1
max: Infinity
},
{
group: 'buckets',
name: 'split',
icon: 'fa fa-th',
title: 'Rows & Columns',
title: 'Split Chart',
min: 0,
max: 1
}

View file

@ -9,11 +9,18 @@ define(function (require) {
// Returns an array x axis values
return _.chain(objKeys)
.pairs()
.map(function (d) {
return d[1].isNumber ? +d[0] : d[0];
})
.value();
.pairs()
.sortBy(function (d) {
// sort by number
if (d[1].isNumber) {
return +d[0];
}
return;
})
.map(function (d) {
return d[1].isNumber ? +d[0] : d[0];
})
.value();
};
};
});

View file

@ -13,10 +13,10 @@ define(function (require) {
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]), isNumber: _.isNumber(key) };
{ index: i, isNumber: _.isNumber(key) } : { index: Math.max(i, uniqueXValues[key].index), isNumber: _.isNumber(key) };
});
// returns an object with unique x values in the correct order
// returns an object with unique x values
return uniqueXValues;
};
};

View file

@ -1,8 +0,0 @@
define(function (require) {
return function LayoutTypeFactory(Private) {
// visLib layout types
return {
histogram: Private(require('components/vislib/components/layouts/types/column_layout'))
};
};
});

View file

@ -38,9 +38,50 @@ define(function (require) {
return [this.data];
};
// Function to determine whether to display the legend or not
// Displays legend when more than one series of data present
Data.prototype.isLegendShown = function () {
var isLegend = false;
var visData;
if (this.data.rows) {
visData = this.data.rows;
} else if (this.data.columns) {
visData = this.data.columns;
} else {
visData = [this.data];
}
_.forEach(visData, function countSeriesLength(obj) {
var dataLength = obj.series ? obj.series.length : obj.slices.children.length;
if (dataLength > 1) {
isLegend = true;
}
});
return isLegend;
};
Data.prototype.pieData = function () {
if (!this.data.slices) {
return this.data.rows ? this.data.rows : this.data.columns;
}
return [this.data];
};
// Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter`
Data.prototype.get = function (thing) {
var data = this.chartData();
var data;
if (this.data.rows) {
data = this.data.rows;
} else if (this.data.columns) {
data = this.data.columns;
} else {
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
@ -62,9 +103,10 @@ define(function (require) {
return values;
};
// TODO: need to make this more generic
Data.prototype.shouldBeStacked = function (series) {
// Series should be an array
if (series.length > 1) {
if (this._attr.type === 'histogram' && series.length > 1) {
return true;
}
return false;
@ -78,7 +120,10 @@ define(function (require) {
// for each object in the dataArray,
// push the calculated y value to the initialized array (arr)
_.forEach(this.flatten(), function (series) {
arr.push(self.getYStackMax(series));
if (self.shouldBeStacked(series)) {
return arr.push(self.getYStackMax(series));
}
return arr.push(self.getYMax(series));
});
// return the largest value from the array
@ -86,22 +131,20 @@ define(function (require) {
};
Data.prototype.stackData = function (series) {
// Determine if the data should be stacked
if (this.shouldBeStacked(series)) {
// if true, stack data
return this._attr.stack(series);
}
return series;
return this._attr.stack(series);
};
Data.prototype.getYStackMax = function (series) {
// Return the calculated y value
return d3.max(this.stackData(series), function (data) {
return d3.max(data, function (d) {
// if stacked, need to add d.y0 + d.y for the y value
if (d.y0) {
return d.y0 + d.y;
}
return d.y0 + d.y;
});
});
};
Data.prototype.getYMax = function (series) {
return d3.max(series, function (data) {
return d3.max(data, function (d) {
return d.y;
});
});
@ -124,7 +167,12 @@ define(function (require) {
// Return a function that does color lookup on labels
Data.prototype.getColorFunc = function () {
return color(this.getLabels(this.data));
return color(this.getLabels());
};
// Return a function that does color lookup on names for pie charts
Data.prototype.getPieColorFunc = function () {
return color(this.get('names'));
};
return Data;

View file

@ -0,0 +1,82 @@
define(function (require) {
return function DispatchClass(d3, Private) {
var _ = require('lodash');
/**
* Events Class
*/
function Dispatch(vis, chartData) {
if (!(this instanceof Dispatch)) {
return new Dispatch(vis, chartData);
}
var type = vis._attr.type;
this.vis = vis;
this.chartData = chartData;
this.color = type === 'pie' ? vis.data.getPieColorFunc() : vis.data.getColorFunc();
this._attr = _.defaults(vis._attr || {}, {
yValue: function (d) {
return d.y;
},
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout')
});
}
// Response to `click` and `hover` events
Dispatch.prototype.eventResponse = function (d, i) {
var label = d.label ? d.label : d.name;
var getYValue = this._attr.yValue;
var color = this.color;
var chartData = this.chartData;
var attr = this._attr;
var vis = this.vis;
return {
value : getYValue(d, i),
point : d,
label : label,
color : color(label),
pointIndex: i,
series : chartData.series,
config : attr,
data : chartData,
e : d3.event,
vis : vis
};
};
// Add brush to the svg
Dispatch.prototype.addBrush = function (xScale, svg) {
var dispatch = this._attr.dispatch;
var attr = this._attr;
var chartData = this.chartData;
var isBrush = this._attr.addBrushing;
var height = this._attr.height;
var margin = this._attr.margin;
// Brush scale
var brush = d3.svg.brush()
.x(xScale)
.on('brushend', function brushEnd() {
// response returned on brush
return dispatch.brush({
range: brush.extent(),
config: attr,
e: d3.event,
data: chartData
});
});
// if `addBrushing` is true, add brush canvas
if (isBrush) {
return svg.append('g')
.attr('class', 'brush')
.call(brush)
.selectAll('rect')
.attr('height', height - margin.top - margin.bottom);
}
};
return Dispatch;
};
});

View file

@ -1,15 +1,10 @@
define(function (require) {
var _ = require('lodash');
return function HandlerBaseClass(d3, Private) {
var _ = require('lodash');
var Data = Private(require('components/vislib/lib/data'));
var Layout = Private(require('components/vislib/lib/layout'));
var Legend = Private(require('components/vislib/lib/legend'));
var Layout = Private(require('components/vislib/lib/layout/layout'));
var Tooltip = Private(require('components/vislib/lib/tooltip'));
var XAxis = Private(require('components/vislib/lib/x_axis'));
var YAxis = Private(require('components/vislib/lib/y_axis'));
var AxisTitle = Private(require('components/vislib/lib/axis_title'));
var ChartTitle = Private(require('components/vislib/lib/chart_title'));
/*
* Handles building all the components of the visualization
@ -19,12 +14,12 @@ define(function (require) {
* returns an object with reference to the vis.prototype,
* and news up all the constructors needed to build a visualization
*/
function Handler(vis) {
function Handler(vis, opts) {
if (!(this instanceof Handler)) {
return new Handler(vis);
return new Handler(vis, opts);
}
this.data = new Data(vis.data, vis._attr);
this.data = opts.data || new Data(vis.data, vis._attr);
this.vis = vis;
this.el = vis.el;
this.ChartClass = vis.ChartClass;
@ -33,50 +28,29 @@ define(function (require) {
});
// Visualization constructors
// Add the visualization layout
this.layout = new Layout(this.el, this.data.injectZeros(), this._attr.type);
this.layout = new Layout(vis.el, vis.data, vis._attr.type);
this.xAxis = opts.xAxis;
this.yAxis = opts.yAxis;
this.chartTitle = opts.chartTitle;
this.axisTitle = opts.axisTitle;
// Only add legend if addLegend attribute set
if (this._attr.addLegend) {
this.legend = new Legend(this.vis, this.el, this.data.getLabels(), this.data.getColorFunc(), this._attr);
}
// only add tooltip if addTooltip attribute set
if (this._attr.addTooltip) {
this.tooltip = new Tooltip(this.el, this.data.get('tooltipFormatter'));
}
// add a x axis
this.xAxis = new XAxis({
el: this.el,
xValues: this.data.xValues(),
ordered: this.data.get('ordered'),
xAxisFormatter: this.data.get('xAxisFormatter'),
_attr: this._attr
});
// add a y axis
this.yAxis = new YAxis({
el: this.el,
yMax: this.data.getYMaxValue(),
_attr: this._attr
});
// add axis titles
this.axisTitle = new AxisTitle(this.el, this.data.get('xAxisLabel'), this.data.get('yAxisLabel'));
// add chart titles
this.chartTitle = new ChartTitle(this.el);
if (this._attr.addLegend && this.data.isLegendShown()) {
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,
this.yAxis,
this.xAxis
this.chartTitle,
this.axisTitle,
this.legend,
this.tooltip
], Boolean);
}

View file

@ -0,0 +1,9 @@
define(function (require) {
return function HandlerTypeFactory(Private) {
// handler types
return {
histogram: Private(require('components/vislib/lib/handler/types/column')),
pie: Private(require('components/vislib/lib/handler/types/pie'))
};
};
});

View file

@ -0,0 +1,40 @@
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'));
var Legend = Private(require('components/vislib/lib/legend'));
var XAxis = Private(require('components/vislib/lib/x_axis'));
var YAxis = Private(require('components/vislib/lib/y_axis'));
var AxisTitle = Private(require('components/vislib/lib/axis_title'));
var ChartTitle = Private(require('components/vislib/lib/chart_title'));
return function (vis) {
var data = new Data(injectZeros(vis.data), vis._attr);
var ColumnHandler = 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')),
chartTitle: new ChartTitle(vis.el),
xAxis: new XAxis({
el : vis.el,
xValues : data.xValues(),
ordered : data.get('ordered'),
xAxisFormatter: data.get('xAxisFormatter'),
_attr : vis._attr
}),
yAxis: new YAxis({
el : vis.el,
yMax : data.getYMaxValue(),
_attr: vis._attr
})
});
return ColumnHandler;
};
};
});

View file

@ -0,0 +1,20 @@
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'));
return function (vis) {
var data = new Data(vis.data, vis._attr);
var PieHandler = new Handler(vis, {
legend: new Legend(vis, vis.el, data.get('names'), data.getPieColorFunc(), vis._attr),
chartTitle: new ChartTitle(vis.el)
});
return PieHandler;
};
};
});

View file

@ -2,7 +2,7 @@ define(function (require) {
return function LayoutFactory(d3, Private) {
var _ = require('lodash');
var layoutType = Private(require('components/vislib/layout_types'));
var layoutType = Private(require('components/vislib/lib/layout/layout_types'));
/*
* The Layout Constructor is responsible for rendering the visualization

View file

@ -0,0 +1,9 @@
define(function (require) {
return function LayoutTypeFactory(Private) {
// visLib layout types
return {
histogram: Private(require('components/vislib/lib/layout/types/column_layout')),
pie: Private(require('components/vislib/lib/layout/types/pie_layout'))
};
};
});

View file

@ -0,0 +1,50 @@
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';
}
});
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')
.attr('class', function () {
return divClass;
});
if (!data.slices) {
charts.call(split);
}
});
};
};
});

View file

@ -0,0 +1,39 @@
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.
*/
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')
.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,10 +1,10 @@
define(function (require) {
return function ColumnLayoutFactory(d3, Private) {
var chartSplit = Private(require('components/vislib/components/layouts/splits/column_chart/chart_split'));
var yAxisSplit = Private(require('components/vislib/components/layouts/splits/column_chart/y_axis_split'));
var xAxisSplit = Private(require('components/vislib/components/layouts/splits/column_chart/x_axis_split'));
var chartTitleSplit = Private(require('components/vislib/components/layouts/splits/column_chart/chart_title_split'));
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.

View file

@ -0,0 +1,66 @@
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
* a `parent` DOM element, a DOM `type` (e.g. div, svg, etc),
* and a `class` (required). Each child can omit the parent object,
* but must include a type and class.
*
* Optionally, you can specify `datum` to be bound to the DOM
* element, a `splits` function that divides the selected element
* into more DOM elements based on a callback function provided, or
* a children array which nests other layout objects.
*
* Objects in children arrays are children of the current object and return
* DOM elements which are children of their respective parent element.
*/
return function (el, data) {
if (!el || !data) {
throw new Error('Both an el and data need to be specified');
}
return [
{
parent: el,
type: 'div',
class: 'vis-wrapper',
datum: data,
children: [
{
type: 'div',
class: 'y-axis-chart-title',
splits: chartTitleSplit
},
{
type: 'div',
class: 'vis-col-wrapper',
children: [
{
type: 'div',
class: 'chart-wrapper',
splits: chartSplit
},
{
type: 'div',
class: 'x-axis-chart-title',
splits: chartTitleSplit
}
]
},
{
type: 'div',
class: 'legend-col-wrapper'
}
]
}
];
};
};
});

View file

@ -3,8 +3,10 @@ define(function (require) {
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/components/styles/main');
require('css!components/vislib/styles/main');
/*
* Append legend to the visualization
@ -23,18 +25,18 @@ define(function (require) {
this.el = el;
this.labels = labels;
this.color = color;
this.tooltip = new Tooltip(this.el, function (d) { return d; });
this._attr = _.defaults(_attr || {}, {
// Legend specific attributes
'legendClass' : 'legend-col-wrapper',
'blurredOpacity' : 0.3,
'focusOpacity' : 1,
'defaultOpacity' : 1,
'isOpen' : false
'isOpen' : true
});
}
// Add legend header
// Need to change the header icon
Legend.prototype.header = function (el, args) {
return el.append('div')
.attr('class', 'header')
@ -87,7 +89,8 @@ define(function (require) {
var self = this;
// toggle
headerIcon.on('click', function () {
headerIcon
.on('click', function legendClick() {
if (self._attr.isOpen) {
// close legend
visEl.select('ul.legend-ul')
@ -106,6 +109,10 @@ define(function (require) {
}
});
headerIcon
.datum(['Legend'])
.call(self.tooltip.render());
visEl.selectAll('.color')
.on('mouseover', function (d) {
var liClass = '.' + self.colorToClass(self.color(d));

View file

@ -4,7 +4,7 @@ define(function (require) {
var $ = require('jquery');
// Dynamically adds css file
require('css!components/vislib/components/styles/main');
require('css!components/vislib/styles/main');
/*
* Append a tooltip div element to the visualization
@ -24,7 +24,7 @@ define(function (require) {
Tooltip.prototype.render = function () {
var self = this;
return function (selection) {
// if tooltip not appended to body, append one
@ -40,18 +40,12 @@ define(function (require) {
var tooltipDiv = d3.select('.' + self.tooltipClass);
selection.each(function () {
// DOM element on which the tooltip is called
var element = d3.select(this);
// define selections relative to el of tooltip
var chartXoffset;
var chartWidth;
var chartHeight;
var yaxisWidth;
var offset;
var tipWidth;
var tipHeight;
element
.on('mousemove.tip', function (d) {
@ -60,20 +54,20 @@ define(function (require) {
left: d3.event.clientX,
top: d3.event.clientY
};
offset = self.getOffsets(tooltipDiv, mouseMove);
// return text and position for tooltip
return tooltipDiv.datum(d)
.text(self.tooltipFormatter)
.style('visibility', 'visible')
.html(self.tooltipFormatter)
.style('display', 'block')
.style('left', mouseMove.left + offset.left + 'px')
.style('top', mouseMove.top - offset.top + 'px');
.style('top', mouseMove.top + offset.top + 'px');
})
.on('mouseout.tip', function () {
// hide tooltip
return tooltipDiv.style('visibility', 'hidden');
return tooltipDiv.style('display', 'none');
});
});
};
@ -83,37 +77,30 @@ define(function (require) {
var self = this;
var offset = {top: 10, left: 10};
var container;
var chartXoffset;
var chartYoffset;
var chartWidth;
var chartHeight;
var tipWidth;
var tipHeight;
if ($(self.el).find('.' + self.containerClass)) {
container = $(self.el).find('.' + self.containerClass);
chartXoffset = container.offset().left;
chartYoffset = container.offset().top;
chartWidth = container.width();
chartHeight = container.height();
tipWidth = tooltipDiv[0][0].clientWidth;
tipHeight = tooltipDiv[0][0].clientHeight;
var container = $(self.el).find('.' + self.containerClass);
var chartXOffset = container.offset().left;
var chartYOffset = container.offset().top;
var chartWidth = container.width();
var chartHeight = container.height();
var tipWidth = tooltipDiv[0][0].clientWidth;
var tipHeight = tooltipDiv[0][0].clientHeight;
// change xOffset to keep tooltip within container
// if tip width + xOffset puts it over right edge of container, flip left
// unless flip left puts it over left edge of container
if ((mouseMove.left + offset.left + tipWidth) > (chartXoffset + chartWidth) &&
(mouseMove.left - tipWidth - 10) > chartXoffset) {
if ((mouseMove.left + offset.left + tipWidth) > (chartXOffset + chartWidth) &&
(mouseMove.left - tipWidth - 10) > chartXOffset) {
offset.left = -10 - tipWidth;
}
// change yOffset to keep tooltip within container
if ((mouseMove.top + tipHeight - 10) > (chartYoffset + chartHeight)) {
offset.top = chartYoffset + chartHeight;
if ((mouseMove.top + tipHeight - 10) > (chartYOffset + chartHeight)) {
offset.top = chartYOffset + chartHeight;
}
}
return offset;
};

View file

@ -37,7 +37,6 @@ define(function (require) {
// Return the d3 y axis
YAxis.prototype.getYAxis = function (height) {
var self = this;
var yScale = this.getYScale(height);
// y scale should never be `NaN`
@ -52,7 +51,7 @@ define(function (require) {
.ticks(this.tickScale(height))
.orient('left');
if (self.yScale.domain()[1] <= 10) {
if (this.yScale.domain()[1] <= 10) {
this.yAxis.tickFormat(d3.format('n'));
}
@ -84,9 +83,11 @@ define(function (require) {
return function (selection) {
selection.each(function () {
div = d3.select(this);
width = $(this).width();
height = $(this).height() - margin.top - margin.bottom;
var el = this;
div = d3.select(el);
width = $(el).width();
height = $(el).height() - margin.top - margin.bottom;
// Validate whether width and height are not 0 or `NaN`
self.validateWidthandHeight(width, height);

View file

@ -197,7 +197,7 @@ p.rows-label, p.columns-label {
stroke-width: 1px;
}
path, line, .axis line, .axis path {
.axis line, .axis path {
stroke: #ddd;
fill: none;
shape-rendering: crispEdges;
@ -218,6 +218,7 @@ path, line, .axis line, .axis path {
}
.k4tip, .vis-tooltip {
display: none;
line-height: 1.1;
font-size: 12px;
font-weight: normal;
@ -227,9 +228,8 @@ path, line, .axis line, .axis path {
border-radius: 4px;
position: fixed;
z-index: 120;
visibility: hidden;
word-wrap: break-word;
max-width: 140px;
max-width: 40%;
}
.rect {

View file

@ -3,12 +3,12 @@ define(function (require) {
var $ = require('jquery');
var _ = require('lodash');
var Handler = Private(require('components/vislib/lib/handler'));
var ResizeChecker = Private(require('components/vislib/lib/resize_checker'));
var Events = Private(require('factories/events'));
var chartTypes = Private(require('components/vislib/vis_types'));
var handlerTypes = Private(require('components/vislib/lib/handler/handler_types'));
var chartTypes = Private(require('components/vislib/visualizations/vis_types'));
var errors = require('errors');
require('css!components/vislib/components/styles/main.css');
require('css!components/vislib/styles/main.css');
/*
* Visualization controller. Exposed API for creating visualizations.
@ -37,13 +37,15 @@ define(function (require) {
// Exposed API for rendering charts.
Vis.prototype.render = function (data) {
var chartType = this._attr.type;
if (!data) {
throw new Error('No valid data!');
}
// Save data to this object and new up the Handler constructor
this.data = data;
this.handler = new Handler(this);
this.handler = handlerTypes[chartType](this) || handlerTypes.column(this);
try {
this.handler.render();

View file

@ -2,6 +2,9 @@ define(function (require) {
return function ChartBaseClass(d3, Private) {
var _ = require('lodash');
var Legend = Private(require('components/vislib/lib/legend'));
var Dispatch = Private(require('components/vislib/lib/dispatch'));
/*
* Base Class for all visualizations.
* Exposes a render method.
@ -14,6 +17,7 @@ define(function (require) {
this.vis = vis;
this.chartEl = el;
this.chartData = chartData;
this.events = new Dispatch(vis, chartData);
this._attr = _.defaults(vis._attr || {}, {});
}
@ -22,6 +26,10 @@ define(function (require) {
return d3.select(this.chartEl).call(this.draw());
};
Chart.prototype.colorToClass = function (label) {
return 'color ' + Legend.prototype.colorToClass.call(null, label);
};
return Chart;
};
});

View file

@ -4,11 +4,10 @@ define(function (require) {
var $ = require('jquery');
var Chart = Private(require('components/vislib/visualizations/_chart'));
var Legend = Private(require('components/vislib/lib/legend'));
var errors = require('errors');
// Dynamically adds css file
require('css!components/vislib/components/styles/main');
require('css!components/vislib/styles/main');
/*
* Column chart visualization => vertical bars, stacked bars
@ -23,26 +22,10 @@ define(function (require) {
// Column chart specific attributes
this._attr = _.defaults(vis._attr || {}, {
xValue: function (d, i) { return d.x; },
yValue: function (d, i) { return d.y; },
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout'),
yValue: function (d, i) { return d.y; }
});
}
// Response to `click` and `hover` events
ColumnChart.prototype.eventResponse = function (d, i) {
return {
value: this._attr.yValue(d, i),
point: d,
label: d.label,
color: this.vis.data.getColorFunc()(d.label),
pointIndex: i,
series : this.chartData.series,
config : this._attr,
data : this.chartData,
e : d3.event
};
};
// Stack data
// TODO: refactor so that this is called from the data module
ColumnChart.prototype.stackData = function (data) {
@ -60,38 +43,14 @@ define(function (require) {
}));
};
// Add brush to the svg
ColumnChart.prototype.addBrush = function (xScale, svg) {
var self = this;
// Brush scale
var brush = d3.svg.brush()
.x(xScale)
.on('brushend', function brushEnd() {
// response returned on brush
return self._attr.dispatch.brush({
range: brush.extent(),
config: self._attr,
e: d3.event,
data: self.chartData
});
});
// if `addBrushing` is true, add brush canvas
if (self._attr.addBrushing) {
svg.append('g')
.attr('class', 'brush')
.call(brush)
.selectAll('rect')
.attr('height', this._attr.height - this._attr.margin.top - this._attr.margin.bottom);
}
};
ColumnChart.prototype.addBars = function (svg, layers) {
var self = this;
var data = this.chartData;
var color = this.vis.data.getColorFunc();
var xScale = this.vis.xAxis.xScale;
var yScale = this.vis.yAxis.yScale;
var tooltip = this.vis.tooltip;
var isTooltip = this._attr.addTooltip;
var layer;
var bars;
@ -114,71 +73,69 @@ define(function (require) {
// enter
bars.enter()
.append('rect')
.attr('class', function (d) {
return 'color ' + Legend.prototype.colorToClass.call(this, color(d.label));
})
.attr('fill', function (d) {
return color(d.label);
});
.append('rect')
.attr('class', function (d) {
return self.colorToClass(color(d.label));
})
.attr('fill', function (d) {
return color(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 bars;
};
ColumnChart.prototype.addBarEvents = function (bars) {
var self = this;
var tooltip = this.vis.tooltip;
var isTooltip = this._attr.addTooltip;
var dispatch = this._attr.dispatch;
bars
.on('mouseover.bar', function (d, i) {
d3.select(this)
.classed('hover', true)
.style('stroke', '#333')
.style('cursor', 'pointer');
dispatch.hover(self.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('click.bar', function (d, i) {
dispatch.click(self.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('mouseout.bar', function () {
d3.select(this).classed('hover', false)
.style('stroke', null);
});
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());
}
return bars;
};
ColumnChart.prototype.addBarEvents = function (bars) {
var events = this.events;
var dispatch = this.events._attr.dispatch;
bars
.on('mouseover.bar', function (d, i) {
d3.select(this)
.classed('hover', true)
.style('stroke', '#333')
.style('cursor', 'pointer');
dispatch.hover(events.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('click.bar', function (d, i) {
dispatch.click(events.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('mouseout.bar', function () {
d3.select(this).classed('hover', false)
.style('stroke', null);
});
};
ColumnChart.prototype.draw = function () {
@ -191,6 +148,7 @@ define(function (require) {
var elHeight = this._attr.height = $elem.height();
var minWidth = 20;
var minHeight = 20;
var isEvents = this._attr.addEvents;
var div;
var svg;
var width;
@ -222,13 +180,15 @@ define(function (require) {
.attr('transform', 'translate(0,' + margin.top + ')');
// addBrush canvas
self.addBrush(xScale, svg);
self.events.addBrush(xScale, svg);
// add bars
bars = self.addBars(svg, layers);
// add events to bars
self.addBarEvents(bars);
if (isEvents) {
self.addBarEvents(bars);
}
// chart base line
var line = svg.append('line')

View file

@ -0,0 +1,324 @@
define(function (require) {
return function LineChartFactory(d3, Private) {
var _ = require('lodash');
var $ = require('jquery');
var Chart = Private(require('components/vislib/visualizations/_chart'));
// Dynamically adds css file
require('css!components/vislib/components/styles/main');
_(LineChart).inherits(Chart);
function LineChart(vis, chartEl, chartData) {
if (!(this instanceof LineChart)) {
return new LineChart(vis, chartEl, chartData);
}
LineChart.Super.apply(this, arguments);
// Line chart specific attributes
this._attr = _.defaults(vis._attr || {}, {
interpolate: 'linear',
xValue: function (d) { return d.x; },
yValue: function (d) { return d.y; },
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout')
});
}
// Response to `click` and `hover` events
LineChart.prototype.eventResponse = function (d, i) {
var getYValue = this._attr.yValue;
var color = this.vis.data.getColorFunc();
var series = this.chartData.series;
var config = this._attr;
var chartData = this.chartData;
return {
value: getYValue(d, i),
point: d,
label: d.label,
color: color(d.label),
pointIndex: i,
series: series,
config: config,
data: chartData,
e: d3.event
};
};
LineChart.prototype.addCircleEvents = function (circles) {
var self = this;
var tooltip = this.vis.tooltip;
var isTooltip = this._attr.addTooltip;
var dispatch = this._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');
dispatch.hover(self.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('click.circle', function clickCircle(d, i) {
dispatch.click(self.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('mouseout.circle', function mouseOutCircle() {
var circle = this;
d3.select(circle)
.classed('hover', false)
.style('stroke', null);
});
// Add tooltip
if (isTooltip) {
circles.call(tooltip.render());
}
};
// Add brush to the svg
LineChart.prototype.addBrush = function (xScale, svg) {
var brushDispatch = this._attr.dispatch.brush;
var height = this._attr.height;
var margin = this._attr.margin;
var self = this;
// Brush scale
var brush = d3.svg.brush()
.x(xScale)
.on('brushend', function brushEnd() {
// response returned on brush
return brushDispatch({
range: brush.extent(),
config: self._attr,
e: d3.event,
data: self.chartData
});
});
if (self._attr.addBrushing) {
// add brush canvas
svg.append('g')
.attr('class', 'brush')
.call(brush)
.selectAll('rect')
.attr('height', height - margin.top - margin.bottom);
}
};
LineChart.prototype.addCircles = function (svg, data) {
var self = this;
var color = this.vis.data.getColorFunc();
var xScale = this.vis.xAxis.xScale;
var yScale = this.vis.yAxis.yScale;
var ordered = this.vis.data.get('ordered');
var circleRadius = 4;
var circleStrokeWidth = 1;
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 self.colorToClass(color(d.label));
})
.attr('fill', function (d) {
return color(d.label);
})
.attr('stroke', function strokeColor(d) {
return color(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) {
return yScale(d.y);
})
.attr('r', circleRadius);
return circles;
};
LineChart.prototype.addLines = function (svg, data) {
var self = this;
var xScale = this.vis.xAxis.xScale;
var yScale = this.vis.yAxis.yScale;
var xAxisFormatter = this.vis.data.get('xAxisFormatter');
var color = this.vis.data.getColorFunc();
var ordered = this.vis.data.get('ordered');
var interpolate = this._attr.interpolate;
var line = d3.svg.line()
.interpolate(interpolate)
.x(function x(d) {
if (ordered && ordered.date) {
return xScale(d.x);
}
return xScale(d.x) + xScale.rangeBand() / 2;
})
.y(function y(d) {
return yScale(d.y);
});
var lines;
lines = svg
.selectAll('.lines')
.data(data)
.enter()
.append('g')
.attr('class', 'lines');
lines.append('path')
.attr('class', function lineClass(d) {
return self.colorToClass(color(d.label));
})
.attr('d', function lineD(d) {
return line(d.values);
})
.attr('fill', 'none')
.attr('stroke', function lineStroke(d) {
return color(d.label);
})
.attr('stroke-width', 2);
return lines;
};
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;
// Creating clipPath
return svg
.attr('clip-path', 'url(#chart-area)')
.append('clipPath')
.attr('id', 'chart-area')
.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);
};
LineChart.prototype.draw = function () {
// Attributes
var self = this;
var $elem = $(this.chartEl);
var margin = this._attr.margin;
var elWidth = this._attr.width = $elem.width();
var elHeight = this._attr.height = $elem.height();
var xScale = this.vis.xAxis.xScale;
var chartToSmallError = 'The height and/or width of this container is too small for this chart.';
var minWidth = 20;
var minHeight = 20;
var startLineX = 0;
var lineStrokeWidth = 1;
var div;
var svg;
var width;
var height;
var lines;
var circles;
return function (selection) {
selection.each(function (data) {
var el = this;
var layers = data.series.map(function mapSeries(d) {
var label = d.label;
return d.values.map(function mapValues(e, i) {
return {
label: label,
x: self._attr.xValue.call(d.values, e, i),
y: self._attr.yValue.call(d.values, e, i)
};
});
});
// 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 + ')');
// add clipPath to hide circles when they go out of bounds
self.addClipPath(svg, width, height);
// addBrush canvas
self.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)
.attr('y1', height)
.attr('x2', width)
.attr('y2', height)
.style('stroke', '#ddd')
.style('stroke-width', lineStrokeWidth);
return svg;
});
};
};
return LineChart;
};
});

View file

@ -0,0 +1,146 @@
define(function (require) {
return function PieChartFactory(d3, Private) {
var _ = require('lodash');
var $ = require('jquery');
var Chart = Private(require('components/vislib/visualizations/_chart'));
// Dynamically adds css file
require('css!components/vislib/styles/main');
/*
* Column chart visualization => vertical bars, stacked bars
*/
_(PieChart).inherits(Chart);
function PieChart(vis, chartEl, chartData) {
if (!(this instanceof PieChart)) {
return new PieChart(vis, chartEl, chartData);
}
PieChart.Super.apply(this, arguments);
this._attr = _.defaults(vis._attr || {}, {
getSize: function (d) { return d.size; },
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout')
});
}
PieChart.prototype.addPathEvents = function (path) {
var events = this.events;
var dispatch = this.events._attr.dispatch;
path
.on('mouseover.pie', function mouseOverPie(d, i) {
d3.select(this)
.classed('hover', true)
.style('cursor', 'pointer');
dispatch.hover(events.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('click.pie', function clickPie(d, i) {
dispatch.click(events.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('mouseout.pie', function mouseOutPie() {
d3.select(this)
.classed('hover', false)
.style('stroke', null);
});
};
PieChart.prototype.addPath = function (width, height, svg, slices) {
var isDonut = this._attr.isDonut;
var radius = Math.min(width, height) / 2;
var color = this.vis.data.getPieColorFunc();
var partition = d3.layout.partition()
.sort(null)
.value(function (d) {
return d.size;
});
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var arc = d3.svg.arc()
.startAngle(function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function (d) {
// option for a single layer, i.e pie chart
if (d.depth === 1 && !isDonut) {
// return no inner radius
return 0;
}
return Math.max(0, y(d.y));
})
.outerRadius(function (d) {
return Math.max(0, y(d.y + d.dy));
});
var tooltip = this.vis.tooltip;
var isTooltip = this._attr.addTooltip;
var self = this;
var path;
path = svg
.datum(slices)
.selectAll('path')
.data(partition.nodes)
.enter()
.append('path')
.attr('d', arc)
.attr('class', function (d) {
if (d.depth === 0) { return; }
return self.colorToClass(color(d.name));
})
.style('stroke', '#fff')
.style('fill', function (d) {
return color(d.name);
})
.attr('fill-rule', 'evenodd');
// Add tooltip
if (isTooltip) {
path.call(tooltip.render());
}
return path;
};
PieChart.prototype.draw = function () {
var self = this;
var isEvents = this._attr.addEvents;
return function (selection) {
selection.each(function (data) {
var slices = data.slices;
var el = this;
var div = d3.select(el);
var width = $(el).width();
var height = $(el).height();
var svg = div.append('svg')
.attr('width', width)
.attr('height', height)
.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);
}
return svg;
});
};
};
return PieChart;
};
});

View file

@ -2,7 +2,10 @@ define(function (require) {
return function VisTypeFactory(Private) {
// visLib visualization types
return {
histogram: Private(require('components/vislib/visualizations/column_chart'))
histogram: Private(require('components/vislib/visualizations/column_chart')),
pie: Private(require('components/vislib/visualizations/pie_chart')),
line: Private(require('components/vislib/visualizations/line_chart'))
};
};
});

View file

@ -19,7 +19,6 @@
@sidebar-active-hover-color: @component-active-color;
@vis-editor-sidebar-basis: (100/12) * 2%; // two of twelve columns
@vis-editor-sidebar-min-width: 300px;
@vis-editor-nesting-width: 3px;
@vis-editor-nesting-expand-width: 10px;
@vis-editor-agg-editor-spacing: 5px;
@vis-editor-sidebar-min-width: 350px;
@vis-editor-nesting-width: 8px;
@vis-editor-agg-editor-spacing: 5px;

View file

@ -210,6 +210,14 @@ notifications {
}
}
.navbtn {
.button-variant(@navbar-default-color; @navbar-default-bg; @navbar-default-border);
}
.navbtn-inverse {
.button-variant(@navbar-default-color; @navbar-default-bg; @navbar-default-border);
}
// right section of the main nav base
.navbar-static-top .navbar-right {
font-size: @font-size-small;

View file

@ -28,6 +28,9 @@ parser = OptionParser.new do |opts|
opts.on('-p', '--port PORT', 'Kibana port') do |arg|
options[:port] = arg
end
opts.on('-q', '--quiet', 'Turns off logging') do |arg|
options[:quiet] = arg
end
opts.on('-H', '--host HOST', 'Kibana host') do |arg|
options[:host] = arg
end
@ -68,6 +71,7 @@ Kibana.global_settings[:host] = host || '0.0.0.0'
Kibana.global_settings[:config] = config
Kibana.global_settings[:elasticsearch] = elasticsearch
Kibana.global_settings[:root] = File.expand_path("#{File.dirname(__FILE__)}/../")
Kibana.global_settings[:quiet] = options[:quiet]
# Set the public folder based on whether we are running in production or not.
if ENV['RACK_ENV'] == ('production')

View file

@ -37,7 +37,7 @@ module Kibana
end
configure :production do
use JSONLogger, settings.logger
use JSONLogger, settings.logger unless Kibana.global_settings[:quiet]
end
configure :quiet do
@ -45,7 +45,7 @@ module Kibana
end
configure :development do
use ColorLogger, settings.logger
use ColorLogger, settings.logger unless Kibana.global_settings[:quiet]
end
error do

View file

@ -17,6 +17,7 @@ module Kibana
}
def self.log(msg)
return if Kibana.global_settings[:quiet]
if ENV['RACK_ENV'] == 'production'
data = {
"@timestamp" => Time.now.iso8601,

View file

@ -11,7 +11,7 @@ module.exports = {
'<%= src %>/kibana/apps/visualize/styles/visualization.less',
'<%= src %>/kibana/apps/visualize/styles/main.less',
'<%= src %>/kibana/styles/main.less',
'<%= src %>/kibana/components/vislib/components/styles/main.less',
'<%= src %>/kibana/components/vislib/styles/main.less',
'<%= src %>/kibana/components/**/*.less'
],
expand: true,

View file

@ -25,7 +25,7 @@ module.exports = {
},
{
browserName: 'firefox',
platform: 'Linux',
platform: 'Windows 7',
},
{
browserName: 'internet explorer',

View file

@ -7,10 +7,77 @@ define(function (require) {
describe('VisLib _chart Test Suite', function () {
var ColumnChart;
var Chart;
var Data;
var Vis;
var chartData = {};
var vis;
var el;
var myChart;
var config;
var data = {
hits : 621,
label : '',
ordered : {
date: true,
interval: 30000,
max : 1408734982458,
min : 1408734082458
},
series : [
{
values: [
{
x: 1408734060000,
y: 8
},
{
x: 1408734090000,
y: 23
},
{
x: 1408734120000,
y: 30
},
{
x: 1408734150000,
y: 28
},
{
x: 1408734180000,
y: 36
},
{
x: 1408734210000,
y: 30
},
{
x: 1408734240000,
y: 26
},
{
x: 1408734270000,
y: 22
},
{
x: 1408734300000,
y: 29
},
{
x: 1408734330000,
y: 24
}
]
}
],
tooltipFormatter: function (datapoint) {
return datapoint;
},
xAxisFormatter: function (thing) {
return thing;
},
xAxisLabel: 'Date Histogram',
yAxisLabel: 'Count'
};
beforeEach(function () {
module('ChartBaseClass');
@ -19,15 +86,24 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
Vis = Private(require('components/vislib/vis'));
Data = Private(require('components/vislib/lib/data'));
ColumnChart = Private(require('components/vislib/visualizations/column_chart'));
Chart = Private(require('components/vislib/visualizations/_chart'));
el = d3.select('body').append('div').attr('class', 'column-chart');
vis = {
_attr: {
stack: d3.layout.stack()
}
config = {
type: 'histogram',
shareYAxis: true,
addTooltip: true,
addLegend: true,
stack: d3.layout.stack(),
};
vis = new Vis(el[0][0], config);
vis.data = new Data(data, config);
myChart = new ColumnChart(vis, el, chartData);
});
});

View file

@ -73,7 +73,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
layoutType = Private(require('components/vislib/layout_types'));
layoutType = Private(require('components/vislib/lib/layout/layout_types'));
el = d3.select('body').append('div').attr('class', 'visualization');
columnLayout = layoutType.histogram(el, data);
});

View file

@ -12,6 +12,7 @@ define(function (require) {
var Data;
var Handler;
var handler;
var ColumnHandler;
var vis;
var el;
var example;
@ -92,7 +93,8 @@ define(function (require) {
inject(function (d3, Private) {
Vis = Private(require('components/vislib/vis'));
Data = Private(require('components/vislib/lib/data'));
Handler = Private(require('components/vislib/lib/handler'));
Handler = Private(require('components/vislib/lib/handler/handler'));
ColumnHandler = Private(require('components/vislib/lib/handler/types/column'));
el = d3.select('body').append('div')
.attr('class', 'visualize');
@ -105,14 +107,9 @@ define(function (require) {
};
vis = new Vis(el[0][0], config);
vis.data = data;
handler = new Handler({
vis: vis,
el: el[0][0],
data: data,
ChartClass: vis.ChartClass,
_attr: config
});
handler = ColumnHandler(vis);
// handler.render(data);
});

View file

@ -77,8 +77,8 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
Layout = Private(require('components/vislib/lib/layout'));
xAxisSplit = Private(require('components/vislib/components/layouts/splits/column_chart/x_axis_split'));
Layout = Private(require('components/vislib/lib/layout/layout'));
xAxisSplit = Private(require('components/vislib/lib/layout/splits/column_chart/x_axis_split'));
el = d3.select('body').append('div')
.attr('class', 'visualize-chart');

View file

@ -14,7 +14,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
layoutType = Private(require('components/vislib/layout_types'));
layoutType = Private(require('components/vislib/lib/layout/layout_types'));
layoutFunc = layoutType.histogram;
});
});

View file

@ -144,10 +144,10 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
chartSplit = Private(require('components/vislib/components/layouts/splits/column_chart/chart_split'));
chartTitleSplit = Private(require('components/vislib/components/layouts/splits/column_chart/chart_title_split'));
xAxisSplit = Private(require('components/vislib/components/layouts/splits/column_chart/x_axis_split'));
yAxisSplit = Private(require('components/vislib/components/layouts/splits/column_chart/y_axis_split'));
chartSplit = Private(require('components/vislib/lib/layout/splits/column_chart/chart_split'));
chartTitleSplit = Private(require('components/vislib/lib/layout/splits/column_chart/chart_title_split'));
xAxisSplit = Private(require('components/vislib/lib/layout/splits/column_chart/x_axis_split'));
yAxisSplit = Private(require('components/vislib/lib/layout/splits/column_chart/y_axis_split'));
el = d3.select('body').append('div')
.attr('class', 'visualization')

View file

@ -14,7 +14,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
visTypes = Private(require('components/vislib/vis_types'));
visTypes = Private(require('components/vislib/visualizations/vis_types'));
visFunc = visTypes.histogram;
});
});