diff --git a/package.json b/package.json index fbca397f7acffa..ee269cb8144d15 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/plugins/kbn_vislib_vis_types/public/editors/sankey.html b/src/plugins/kbn_vislib_vis_types/public/editors/sankey.html new file mode 100644 index 00000000000000..06369349c4d6d2 --- /dev/null +++ b/src/plugins/kbn_vislib_vis_types/public/editors/sankey.html @@ -0,0 +1,7 @@ + +
+ +
+ diff --git a/src/plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js b/src/plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js index 6aa4e07a0230fd..5f6d1ea3e178a1 100644 --- a/src/plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js +++ b/src/plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js @@ -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')); }); diff --git a/src/plugins/kbn_vislib_vis_types/public/sankey.js b/src/plugins/kbn_vislib_vis_types/public/sankey.js new file mode 100644 index 00000000000000..ade07eedaff883 --- /dev/null +++ b/src/plugins/kbn_vislib_vis_types/public/sankey.js @@ -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.', + 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' + } + ]) + }); + }; +}); diff --git a/src/ui/public/Vis/Vis.js b/src/ui/public/Vis/Vis.js index 79f7b6bcf5d1b1..9794c745312feb 100644 --- a/src/ui/public/Vis/Vis.js +++ b/src/ui/public/Vis/Vis.js @@ -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) { diff --git a/src/ui/public/Vis/__tests__/_Vis.js b/src/ui/public/Vis/__tests__/_Vis.js index f72e69e29af9d4..01868027c7ed2f 100644 --- a/src/ui/public/Vis/__tests__/_Vis.js +++ b/src/ui/public/Vis/__tests__/_Vis.js @@ -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); + }); + }); + }); diff --git a/src/ui/public/agg_response/index.js b/src/ui/public/agg_response/index.js index 1d9aeecc101245..7a5785d4c79bc4 100644 --- a/src/ui/public/agg_response/index.js +++ b/src/ui/public/agg_response/index.js @@ -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')) }; }; diff --git a/src/ui/public/agg_response/sankey/__tests__/sankey.js b/src/ui/public/agg_response/sankey/__tests__/sankey.js new file mode 100644 index 00000000000000..4ab3805ca089cf --- /dev/null +++ b/src/ui/public/agg_response/sankey/__tests__/sankey.js @@ -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); + }); + + }); + +}); diff --git a/src/ui/public/agg_response/sankey/sankey.js b/src/ui/public/agg_response/sankey/sankey.js new file mode 100644 index 00000000000000..9f07cf5eae3bd7 --- /dev/null +++ b/src/ui/public/agg_response/sankey/sankey.js @@ -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; + }; + }; +}); diff --git a/src/ui/public/vislib/__tests__/visualizations/sankey_chart.js b/src/ui/public/vislib/__tests__/visualizations/sankey_chart.js new file mode 100644 index 00000000000000..4b165e7cc56d3a --- /dev/null +++ b/src/ui/public/vislib/__tests__/visualizations/sankey_chart.js @@ -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); + }); + }); + }); + +}); diff --git a/src/ui/public/vislib/lib/data.js b/src/ui/public/vislib/lib/data.js index 276c1c3f913708..40f5fb17813d25 100644 --- a/src/ui/public/vislib/lib/data.js +++ b/src/ui/public/vislib/lib/data.js @@ -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. diff --git a/src/ui/public/vislib/lib/handler/handler_types.js b/src/ui/public/vislib/lib/handler/handler_types.js index 9d9c160285865b..75063dd7ff0835 100644 --- a/src/ui/public/vislib/lib/handler/handler_types.js +++ b/src/ui/public/vislib/lib/handler/handler_types.js @@ -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')) }; }; diff --git a/src/ui/public/vislib/lib/handler/types/sankey.js b/src/ui/public/vislib/lib/handler/types/sankey.js new file mode 100644 index 00000000000000..96c37d1cc3fa67 --- /dev/null +++ b/src/ui/public/vislib/lib/handler/types/sankey.js @@ -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; + }; + }; +}); diff --git a/src/ui/public/vislib/lib/layout/layout_types.js b/src/ui/public/vislib/lib/layout/layout_types.js index ccaa776c4fe9a4..9037d082895cd4 100644 --- a/src/ui/public/vislib/lib/layout/layout_types.js +++ b/src/ui/public/vislib/lib/layout/layout_types.js @@ -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')) }; }; diff --git a/src/ui/public/vislib/lib/layout/splits/sankey/sankey_split.js b/src/ui/public/vislib/lib/layout/splits/sankey/sankey_split.js new file mode 100644 index 00000000000000..32969a5ef7c606 --- /dev/null +++ b/src/ui/public/vislib/lib/layout/splits/sankey/sankey_split.js @@ -0,0 +1,50 @@ +define(function () { + return function ChartSplitFactory() { + var d3 = require('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 () { + 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) { + 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); + } + }); + }; + }; +}); diff --git a/src/ui/public/vislib/lib/layout/types/sankey_layout.js b/src/ui/public/vislib/lib/layout/types/sankey_layout.js new file mode 100644 index 00000000000000..e85da08abe34b2 --- /dev/null +++ b/src/ui/public/vislib/lib/layout/types/sankey_layout.js @@ -0,0 +1,34 @@ +define(function (require) { + return function ColumnLayoutFactory(Private) { + var d3 = require('d3'); + var sankeySplit = + Private(require('ui/vislib/lib/layout/splits/sankey/sankey_split')); + 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: 'vis-col-wrapper', + children: [ + { + type: 'div', + class: 'chart-wrapper', + splits: sankeySplit + } + ] + } + ] + } + ]; + }; + }; +}); diff --git a/src/ui/public/vislib/styles/_sankey.less b/src/ui/public/vislib/styles/_sankey.less new file mode 100644 index 00000000000000..96f0d4cca8f7f9 --- /dev/null +++ b/src/ui/public/vislib/styles/_sankey.less @@ -0,0 +1,20 @@ +.node rect { + cursor: move; + fill-opacity: .9; + shape-rendering: crispEdges; +} + +.node text { + pointer-events: none; + text-shadow: 0 1px 0 #fff; +} + +.link { + fill: none; + stroke: #000; + stroke-opacity: .2; +} + +.link:hover { + stroke-opacity: .5; +} diff --git a/src/ui/public/vislib/styles/main.less b/src/ui/public/vislib/styles/main.less index f44fe6823d33c8..3dd1746fe7368a 100644 --- a/src/ui/public/vislib/styles/main.less +++ b/src/ui/public/vislib/styles/main.less @@ -7,3 +7,4 @@ @import "./_tooltip"; @import "./_tilemap"; @import "./_alerts"; +@import "./_sankey"; diff --git a/src/ui/public/vislib/visualizations/sankey_chart.js b/src/ui/public/vislib/visualizations/sankey_chart.js new file mode 100644 index 00000000000000..b53c80c045f562 --- /dev/null +++ b/src/ui/public/vislib/visualizations/sankey_chart.js @@ -0,0 +1,144 @@ +define(function (require) { + return function SankeyChartFactory(Private) { + var d3 = require('d3'); + var _ = require('lodash'); + var $ = require('jquery'); + + var S = require('d3-plugins-sankey'); + var formatNumber = d3.format(',.0f'); + var format = function (d) { return formatNumber(d) + ' TWh'; }; + + var Chart = Private(require('ui/vislib/visualizations/_chart')); + var errors = require('ui/errors'); + + /** + * Sankey Chart Visualization + * + * @class SankeyChart + * @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 + */ + _.class(SankeyChart).inherits(Chart); + function SankeyChart(handler, chartEl, chartData) { + if (!(this instanceof SankeyChart)) { + return new SankeyChart(handler, chartEl, chartData); + } + SankeyChart.Super.apply(this, arguments); + + var charts = this.handler.data.getVisData(); + } + + + SankeyChart.prototype._validateContainerSize = function (width, height) { + var minWidth = 20; + var minHeight = 20; + + if (width <= minWidth || height <= minHeight) { + throw new errors.ContainerTooSmall(); + } + }; + + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the sankey chart + */ + SankeyChart.prototype.draw = function () { + 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 color = this.handler.data.getSankeyColorFunc(); + var width; + var height; + var div; + var svg; + + return function (selection) { + selection.each(function (data) { + var energy = data.slices; + div = d3.select(this); + width = elWidth - margin.left - margin.right; + height = elHeight - margin.top - margin.bottom; + + if (!energy.nodes.length) return; + + self._validateContainerSize(width, height); + + svg = div.append('svg') + .attr('width', elWidth) + .attr('height', elHeight) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var sankey = d3.sankey() + .nodeWidth(15) + .nodePadding(10) + .size([width, height]); + + var path = sankey.link(); + + sankey + .nodes(energy.nodes) + .links(energy.links) + .layout(32); + + var link = svg.append('g').selectAll('.link') + .data(energy.links) + .enter().append('path') + .attr('class', 'link') + .attr('d', path) + .style('stroke-width', function (d) { return Math.max(1, d.dy); }) + .sort(function (a, b) { return b.dy - a.dy; }); + + link.append('title') + .text(function (d) { return d.source.name + ' → ' + d.target.name + '\n' + format(d.value); }); + + var node = svg.append('g').selectAll('.node') + .data(energy.nodes) + .enter().append('g') + .attr('class', 'node') + .attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')'; }) + .call(d3.behavior.drag() + .origin(function (d) { return d; }) + .on('dragstart', function () { this.parentNode.appendChild(this); }) + .on('drag', dragmove)); + + node.append('rect') + .attr('height', function (d) { return d.dy; }) + .attr('width', sankey.nodeWidth()) + .style('fill', function (d) { return d.color = color(d.name); }) + .style('stroke', function (d) { return d3.rgb(d.color).darker(2); }) + .append('title') + .text(function (d) { return d.name + '\n' + format(d.value); }); + + node.append('text') + .attr('x', -6) + .attr('y', function (d) { return d.dy / 2; }) + .attr('dy', '.35em') + .attr('text-anchor', 'end') + .attr('transform', null) + .text(function (d) { return d.name; }) + .filter(function (d) { return d.x < width / 2; }) + .attr('x', 6 + sankey.nodeWidth()) + .attr('text-anchor', 'start'); + + function dragmove(d) { + d3.select(this).attr('transform', 'translate(' + d.x + ',' + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ')'); + sankey.relayout(); + link.attr('d', path); + } + + return svg; + }); + }; + }; + + return SankeyChart; + }; +}); diff --git a/src/ui/public/vislib/visualizations/vis_types.js b/src/ui/public/vislib/visualizations/vis_types.js index cc238529043049..1a8ee50eeac41a 100644 --- a/src/ui/public/vislib/visualizations/vis_types.js +++ b/src/ui/public/vislib/visualizations/vis_types.js @@ -14,6 +14,7 @@ define(function (require) { pie: Private(require('ui/vislib/visualizations/pie_chart')), line: Private(require('ui/vislib/visualizations/line_chart')), area: Private(require('ui/vislib/visualizations/area_chart')), + sankey: Private(require('ui/vislib/visualizations/sankey_chart')), tile_map: Private(require('ui/vislib/visualizations/tile_map')) }; }; diff --git a/src/ui/public/vislib_vis_type/VislibVisType.js b/src/ui/public/vislib_vis_type/VislibVisType.js index 32dbb8503d99d8..9d41701ba66e0f 100644 --- a/src/ui/public/vislib_vis_type/VislibVisType.js +++ b/src/ui/public/vislib_vis_type/VislibVisType.js @@ -23,6 +23,7 @@ define(function (require) { } this.listeners = opts.listeners || {}; + this.sankeyConverter = opts.sankeyConverter || false; } VislibVisType.prototype.createRenderbot = function (vis, $el) { diff --git a/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js b/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js index a623a83d08e9a9..2426128fc440f1 100644 --- a/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js +++ b/src/ui/public/vislib_vis_type/__tests__/_buildChartData.js @@ -24,6 +24,7 @@ describe('renderbot#buildChartData', function () { var football = {}; var renderbot = { vis: { + isSankey: _.constant(false), isHierarchical: _.constant(true) } }; @@ -40,6 +41,7 @@ describe('renderbot#buildChartData', function () { it('calls tabify to simplify the data into a table', function () { var renderbot = { vis: { + isSankey: _.constant(false), isHierarchical: _.constant(false) } }; @@ -56,6 +58,7 @@ describe('renderbot#buildChartData', function () { var chart = { hits: 1, rows: [], columns: [] }; var renderbot = { vis: { + isSankey: _.constant(false), isHierarchical: _.constant(false), type: { responseConverter: _.constant(chart) @@ -77,6 +80,7 @@ describe('renderbot#buildChartData', function () { var renderbot = { vis: { + isSankey: _.constant(false), isHierarchical: _.constant(false), type: { responseConverter: converter diff --git a/src/ui/public/vislib_vis_type/buildChartData.js b/src/ui/public/vislib_vis_type/buildChartData.js index 29f956f9e9693c..458e0a45aba707 100644 --- a/src/ui/public/vislib_vis_type/buildChartData.js +++ b/src/ui/public/vislib_vis_type/buildChartData.js @@ -11,6 +11,10 @@ define(function (require) { return aggResponse.hierarchical(vis, esResponse); } + if (vis.isSankey()) { + return aggResponse.sankey(vis, esResponse); + } + var tableGroup = aggResponse.tabify(vis, esResponse, { canSplit: true, asAggConfigResults: true