diff --git a/package.json b/package.json index 5422d2386050f..b16b2761c7c5d 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "bootstrap": "3.3.6", "brace": "0.5.1", "bunyan": "1.7.1", + "chart.js": "2.1.3", "clipboard": "1.5.5", "commander": "2.8.1", "css-loader": "0.17.0", diff --git a/src/ui/public/vislib/styles/_legend.less b/src/ui/public/vislib/styles/_legend.less index ddd2ae39d7785..fcfa67cae294d 100644 --- a/src/ui/public/vislib/styles/_legend.less +++ b/src/ui/public/vislib/styles/_legend.less @@ -20,6 +20,10 @@ visualize-legend { flex-direction: row; padding-top: 5px; + &.fixed-width { + width: 200px; + } + .header { cursor: pointer; width: 15px; diff --git a/src/ui/public/visualize/visualize.html b/src/ui/public/visualize/visualize.html index 5d8f7763033bb..895457a142d21 100644 --- a/src/ui/public/visualize/visualize.html +++ b/src/ui/public/visualize/visualize.html @@ -13,5 +13,10 @@

No results found

class="visualize-chart"> +
+ + +
+
diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index 86c9a5beae6be..a546400f2f823 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -6,6 +6,7 @@ import _ from 'lodash'; import RegistryVisTypesProvider from 'ui/registry/vis_types'; import uiModules from 'ui/modules'; import visualizeTemplate from 'ui/visualize/visualize.html'; +import Chart from 'chart'; uiModules .get('kibana/directive') .directive('visualize', function (Notifier, SavedVis, indexPatterns, Private, config, $timeout) { @@ -17,6 +18,130 @@ uiModules location: 'Visualize' }); + function esRespConvertorFactory($el, $legend, chartType) { + chartType = { + pie: 'pie', + line: 'line', + area: 'line', + histogram: 'bar' + }[chartType]; + let myChart; + function makeColor(num) { + let hexStr = Math.round(num).toString(16); + while (hexStr.length / 6 !== 1) { + hexStr = '0' + hexStr; + } + return '#' + hexStr; + } + const isPieChart = (chartType === 'pie'); + function decodeBucketData(aggConfigs, aggregations) { + const datasetMap = {}; + const xAxisLabels = []; + // If there is no data + if (!aggConfigs) { return datasetMap; } + + const maxAggDepth = aggConfigs.length - 1; + let currDepth = 0; + // meant to recursively crawl through es aggregations + // to make sense of the data for a charting library + function decodeBucket(aggConfig, aggResp) { + aggResp.buckets.forEach((bucket) => { + const isFirstAggConfig = currDepth === 0; + + if (isFirstAggConfig) { // Sets the labels for the X-Axis + xAxisLabels.push(bucket.key); + } + if (currDepth < maxAggDepth) { // Crawl through the structure if we should + const nextAggConfig = aggConfigs[++currDepth]; + decodeBucket(nextAggConfig, bucket[nextAggConfig.id]); + currDepth--; + } else { + const isDateBucket = aggConfig.__type.dslName === 'date_histogram'; + const legendLabel = (isFirstAggConfig && isDateBucket && !isPieChart) ? 'Count' : bucket.key; + const dataset = datasetMap[legendLabel] || []; + dataset.push(bucket.doc_count); + datasetMap[legendLabel] = dataset; + } + }); + } + const firstAggConfig = aggConfigs[0]; + decodeBucket(firstAggConfig, aggregations[firstAggConfig.id]); + const legendLabels = []; + const arrDatasets = _.map(datasetMap, (val, key) => { + legendLabels.push(key); + return { + data: val, + label: key, + backgroundColor: [] + }; + }); + // Make some colors for all of the data points. + // This need to be different, instead of looking at all the colors + // you should look at RGB separate and limit the number from there + // then multiply to get your result + arrDatasets.forEach(set => { + const allTheColors = Math.pow(16, 6); + const colorOffset = allTheColors / 8; + let numDataPoints = set.data.length; + const maxColors = allTheColors - (colorOffset * 2); + const difference = maxColors / numDataPoints; + let currColor = colorOffset; + while (numDataPoints-- > 0) { + if (isPieChart) { + set.backgroundColor.push(makeColor(currColor)); + } + currColor += difference; + } + if (!isPieChart) { + set.backgroundColor = set.backgroundColor[0]; + } + }); + + return { + legend: legendLabels, + labels: xAxisLabels, + dataConfigs: arrDatasets + }; + } + return function convertEsRespAndAggConfig(esResp, aggConfigs) { + const aggConfigMap = aggConfigs.byId; + const decodedData = decodeBucketData(aggConfigs.bySchemaGroup.buckets, esResp.aggregations); + if (myChart) { // Not a fan of this, i should be using update + myChart.destroy(); + } + myChart = new Chart($el, { + type: chartType, + data: { + labels: decodedData.labels, + datasets: decodedData.dataConfigs + }, + fill: false, + options: { + legendCallback: function (chartObj) { + const multipleBuckets = aggConfigs.bySchemaGroup.buckets.length > 1; + const legendItems = multipleBuckets || isPieChart ? decodedData.legend : [aggConfigs.bySchemaGroup.metrics[0]._opts.type]; + const itemsHtmlArr = legendItems.map(item => { return '
  • ' + item + '
  • '; }); + return ''; + }, + legend: { + display: false + }, + tooltips: { + callbacks: { + title: function () { return 'hello world'; }, + label: function (item, data) { + const dataset = data.datasets[item.datasetIndex]; + return dataset.label + ': ' + dataset.data[item.index]; + } + } + } + } + }); + + $legend.html(myChart.generateLegend()); + }; + } + return { restrict: 'E', scope : { @@ -29,6 +154,7 @@ uiModules }, template: visualizeTemplate, link: function ($scope, $el, attr) { + const esRespConvertor = esRespConvertorFactory($el.find('#canvas-chart'), $el.find('#chart-legend'), $scope.vis.type.name); let chart; // set in "vis" watcher let minVisChartHeight = 180; @@ -148,6 +274,7 @@ uiModules $scope.$watch('esResp', prereq(function (resp, prevResp) { if (!resp) return; + esRespConvertor(resp, $scope.vis.aggs); $scope.renderbot.render(resp); })); diff --git a/webpackShims/chart.js b/webpackShims/chart.js new file mode 100644 index 0000000000000..ff6f875a02346 --- /dev/null +++ b/webpackShims/chart.js @@ -0,0 +1,9 @@ +/** + * THESE ARE AUTOMATICALLY INCLUDED IN LODASH + * + * use: + * var _ = require('lodash'); + */ + +var Chart = require('node_modules/chart.js/src/chart.js'); +module.exports = Chart;