Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new visualize type: sankey #4832

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"commander": "^2.8.1",
"css-loader": "^0.15.1",
"d3": "^3.5.6",
"d3-plugins-sankey": "1.2.1",
"elasticsearch": "^8.0.1",
"elasticsearch-browser": "^8.0.1",
"expiry-js": "^0.1.7",
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/kbn_vislib_vis_types/public/editors/sankey.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- vis type specific options -->
<div>
<label>
Sankey
</label>
</div>
<vislib-basic-options></vislib-basic-options>
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ define(function (require) {
visTypes.register(require('plugins/kbn_vislib_vis_types/line'));
visTypes.register(require('plugins/kbn_vislib_vis_types/pie'));
visTypes.register(require('plugins/kbn_vislib_vis_types/area'));
visTypes.register(require('plugins/kbn_vislib_vis_types/sankey'));
visTypes.register(require('plugins/kbn_vislib_vis_types/tileMap'));
});
55 changes: 55 additions & 0 deletions src/plugins/kbn_vislib_vis_types/public/sankey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
define(function (require) {
return function HistogramVisType(Private) {
var VislibVisType = Private(require('ui/vislib_vis_type/VislibVisType'));
var Schemas = Private(require('ui/Vis/Schemas'));
var sankeyBuilder = Private(require('ui/agg_response/sankey/sankey'));

return new VislibVisType({
name: 'sankey',
title: 'Sankey chart',
icon: 'fa-sankey-chart',
description: 'Sankey charts are ideal for displaying the material, energy and cost flows.' +
'Pro Tip: Sankey charts are best used sparingly, and with no more than 7 slices per sankey.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? Was this just a search/replace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right^-^.
Wrote a real sankey descirption now.

params: {
defaults: {
shareYAxis: false,
isDonut: false
},
editor: require('plugins/kbn_vislib_vis_types/editors/sankey.html')
},
sankeyConverter: sankeyBuilder,
hierarchicalData: false,
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Slice Size',
min: 1,
aggFilter: ['sum', 'count', 'cardinality', 'min', 'max', 'avg'],
defaults: [
{ schema: 'metric', type: 'count' }
]
},
{
group: 'buckets',
name: 'segment',
icon: 'fa fa-scissors',
title: 'Split Slices',
min: 0,
max: Infinity,
aggFilter: '!geohash_grid'
},
{
group: 'buckets',
name: 'split',
icon: 'fa fa-th',
title: 'Split Chart',
mustBeFirst: true,
min: 0,
max: 1,
aggFilter: '!geohash_grid'
}
])
});
};
});
8 changes: 8 additions & 0 deletions src/ui/public/Vis/Vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ define(function (require) {
}
};

Vis.prototype.isSankey = function () {
if (_.isFunction(this.type.sankeyConverter)) {
return true;
} else {
return false;
}
};

Vis.prototype.hasSchemaAgg = function (schemaName, aggTypeName) {
var aggs = this.aggs.bySchemaName[schemaName] || [];
return aggs.some(function (agg) {
Expand Down
19 changes: 19 additions & 0 deletions src/ui/public/Vis/__tests__/_Vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,23 @@ describe('Vis Class', function () {
});
});

describe('isSankey()', function () {
it('should return true for sankey vis', function () {
var stateFixture = {
type: 'sankey',
aggs: [
{ type: 'count', schema: 'metric' },
{ type: 'terms', schema: 'segment', params: { field: 'extension' }},
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' }},
{ type: 'terms', schema: 'segment', params: { field: 'geo.src' }}
]
};
var vis = new Vis(indexPattern, stateFixture);
expect(vis.isSankey()).to.be(true);
});
it('should return false for non-sankey vis (like pie)', function () {
expect(vis.isSankey()).to.be(false);
});
});

});
1 change: 1 addition & 0 deletions src/ui/public/agg_response/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ define(function (require) {
hierarchical: Private(require('ui/agg_response/hierarchical/build_hierarchical_data')),
pointSeries: Private(require('ui/agg_response/point_series/point_series')),
tabify: Private(require('ui/agg_response/tabify/tabify')),
sankey: Private(require('ui/agg_response/sankey/sankey')),
geoJson: Private(require('ui/agg_response/geo_json/geo_json'))
};
};
Expand Down
75 changes: 75 additions & 0 deletions src/ui/public/agg_response/sankey/__tests__/sankey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

var _ = require('lodash');
var fixtures = require('fixtures/fake_hierarchical_data');
var sinon = require('auto-release-sinon');
var expect = require('expect.js');
var ngMock = require('ngMock');

var Vis;
var Notifier;
var AggConfigs;
var indexPattern;
var buildSankey;

describe('sankey', function () {

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector) {
Notifier = $injector.get('Notifier');
sinon.stub(Notifier.prototype, 'error');

Vis = Private(require('ui/Vis'));
AggConfigs = Private(require('ui/Vis/AggConfigs'));
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
buildSankey = Private(require('ui/agg_response/sankey/sankey'));
}));

describe('threeTermBuckets', function () {
var vis;
var results;

beforeEach(function () {
var id = 1;
vis = new Vis(indexPattern, {
type: 'sankey',
aggs: [
{ type: 'count', schema: 'metric' },
{ type: 'terms', schema: 'segment', params: { field: 'extension' }},
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' }},
{ type: 'terms', schema: 'segment', params: { field: 'geo.src' }}
]
});
// We need to set the aggs to a known value.
_.each(vis.aggs, function (agg) { agg.id = 'agg_' + id++; });
results = buildSankey(vis, fixtures.threeTermBuckets);
});

it('should have nodes and links attributes for the results', function () {
expect(results).to.have.property('slices');
expect(results.slices).to.have.property('nodes');
expect(results.slices).to.have.property('links');
});

it('should have name attributes for the nodes array', function () {
expect(results.slices.nodes).to.have.length(11);
_.each(results.slices.nodes, function (item) {
expect(item).to.have.property('name');
});
expect(results.slices.nodes[0].name).to.equal('png');
});

it('should have source, target and value attributes for the links array', function () {
expect(results.slices.links).to.have.length(16);
_.each(results.slices.links, function (item) {
expect(item).to.have.property('source');
expect(item).to.have.property('target');
expect(item).to.have.property('value');
});
expect(results.slices.links[0].source).to.equal(0);
expect(results.slices.links[0].target).to.equal(1);
expect(results.slices.links[0].value).to.equal(10);
});

});

});
64 changes: 64 additions & 0 deletions src/ui/public/agg_response/sankey/sankey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
define(function (require) {
return function sankeyProvider(Private, Notifier) {
var _ = require('lodash');
var arrayToLinkedList = require('ui/agg_response/hierarchical/_array_to_linked_list');
var notify = new Notifier({
location: 'Sankey chart response converter'
});
var nodes = {};
var links = {};
var lastNode = -1;

function processEntry(aggConfig, metric, aggData, prevNode) {
_.each(aggData.buckets, function (b) {
if (isNaN(nodes[b.key])) {
nodes[b.key] = lastNode + 1;
lastNode = _.max(_.values(nodes));
}
if (aggConfig._previous) {
var k = prevNode + 'sankeysplitchar' + nodes[b.key];
if (isNaN(links[k])) {
links[k] = metric.getValue(b);
} else {
links[k] += metric.getValue(b);
}
}
if (aggConfig._next) {
processEntry(aggConfig._next, metric, b[aggConfig._next.id], nodes[b.key]);
}
});
}

return function (vis, resp) {

var metric = vis.aggs.bySchemaGroup.metrics[0];
var buckets = vis.aggs.bySchemaGroup.buckets;
buckets = arrayToLinkedList(buckets);
if (!buckets) {
return {'slices':{'nodes':[],'links':[]}};
}

var firstAgg = buckets[0];
var aggData = resp.aggregations[firstAgg.id];

if (!firstAgg._next) {
notify.error('need more than one sub aggs');
}

processEntry(firstAgg, metric, aggData, -1);

var invertNodes = _.invert(nodes);
var chart = {
'slices': {
'nodes' : _.map(_.keys(invertNodes), function (k) { return {'name':invertNodes[k]}; }),
'links' : _.map(_.keys(links), function (k) {
var s = k.split('sankeysplitchar');
return {'source': parseInt(s[0]), 'target': parseInt(s[1]), 'value': links[k]};
})
}
};

return chart;
};
};
});
59 changes: 59 additions & 0 deletions src/ui/public/vislib/__tests__/visualizations/sankey_chart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
var d3 = require('d3');
var angular = require('angular');
var expect = require('expect.js');
var ngMock = require('ngMock');
var _ = require('lodash');
var $ = require('jquery');
var fixtures = require('fixtures/fake_hierarchical_data');

var sliceAgg = [
{ type: 'count', schema: 'metric' },
{ type: 'terms', schema: 'segment', params: { field: 'extension' }},
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' }},
{ type: 'terms', schema: 'segment', params: { field: 'geo.src' }}
];

describe('Vislib SankeyChart Class Test Suite for slice data', function () {
var visLibParams = {
type: 'sankey'
};
var vis;
var Vis;
var indexPattern;
var buildSankey;
var data;

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
vis = Private(require('fixtures/vislib/_vis_fixture'))(visLibParams);
Vis = Private(require('ui/Vis'));
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
buildSankey = Private(require('ui/agg_response/sankey/sankey'));

var id = 1;
var stubVis = new Vis(indexPattern, {
type: 'sankey',
aggs: sliceAgg
});

_.each(stubVis.aggs, function (agg) { agg.id = 'agg_' + id++; });

data = buildSankey(stubVis, fixtures.threeTermBuckets);

vis.render(data);
}));

afterEach(function () {
$(vis.el).remove();
vis = null;
});

describe('draw method', function () {
it('should return a function', function () {
vis.handler.charts.forEach(function (chart) {
expect(_.isFunction(chart.draw())).to.be(true);
});
});
});

});
19 changes: 19 additions & 0 deletions src/ui/public/vislib/lib/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,25 @@ define(function (require) {
}));
};

/**
* Returns a function that does color lookup on names for sankey charts
*
* @method getSankeyColorFunc
* @returns {Function} Performs lookup on string and returns hex color
*/
Data.prototype.getSankeyColorFunc = function () {
var data = this.getVisData();
var names = [];

_.forEach(data, function (obj) {
_.forEach(obj.slices.nodes, function (node) {
names.push(node.name);
});
});

return color(names);
};

/**
* ensure that the datas ordered property has a min and max
* if the data represents an ordered date range.
Expand Down
1 change: 1 addition & 0 deletions src/ui/public/vislib/lib/handler/handler_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ define(function (require) {
line: pointSeries.line,
pie: Private(require('ui/vislib/lib/handler/types/pie')),
area: pointSeries.area,
sankey: Private(require('ui/vislib/lib/handler/types/sankey')),
tile_map: Private(require('ui/vislib/lib/handler/types/tile_map'))
};
};
Expand Down
16 changes: 16 additions & 0 deletions src/ui/public/vislib/lib/handler/types/sankey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
define(function (require) {
return function sankeyHandler(Private) {
var Handler = Private(require('ui/vislib/lib/handler/handler'));
var Data = Private(require('ui/vislib/lib/data'));

return function (vis) {
var data = new Data(vis.data, vis._attr);

var sankeyHandler = new Handler(vis, {
data: data
});

return sankeyHandler;
};
};
});
1 change: 1 addition & 0 deletions src/ui/public/vislib/lib/layout/layout_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ define(function (require) {
line: Private(require('ui/vislib/lib/layout/types/column_layout')),
area: Private(require('ui/vislib/lib/layout/types/column_layout')),
pie: Private(require('ui/vislib/lib/layout/types/pie_layout')),
sankey: Private(require('ui/vislib/lib/layout/types/sankey_layout')),
tile_map: Private(require('ui/vislib/lib/layout/types/map_layout'))
};
};
Expand Down
Loading