From 2cff850f13ad83673199b1a6192e68dcbfaf804d Mon Sep 17 00:00:00 2001 From: Khalah Jones-Golden Date: Thu, 5 May 2016 17:00:43 -0400 Subject: [PATCH 1/4] [Charts] Changed to chartjs charting library --- package.json | 1 + src/ui/public/visualize/visualize.html | 2 + src/ui/public/visualize/visualize.js | 90 ++++++++++++++++++++++++++ webpackShims/chart.js | 9 +++ 4 files changed, 102 insertions(+) create mode 100644 webpackShims/chart.js diff --git a/package.json b/package.json index 5422d2386050f..2f76a0be0e506 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.0", "clipboard": "1.5.5", "commander": "2.8.1", "css-loader": "0.17.0", diff --git a/src/ui/public/visualize/visualize.html b/src/ui/public/visualize/visualize.html index 5d8f7763033bb..23ba006b61cce 100644 --- a/src/ui/public/visualize/visualize.html +++ b/src/ui/public/visualize/visualize.html @@ -13,5 +13,7 @@

No results found

class="visualize-chart"> + + diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index 86c9a5beae6be..7dc36409bd8cc 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,93 @@ uiModules location: 'Visualize' }); + function esRespConvertorFactory($el, 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; + } + function decodeBucketData(buckets, aggregations) { + const chartDatasetConfigs = {}; + if (!buckets) { return chartDatasetConfigs; } + const maxAggDepth = buckets.length - 1; + let currDepth = 0; + function decodeBucket(bucket, aggResp) { + const bucketId = bucket.id; + if (!chartDatasetConfigs[bucket.id]) { + chartDatasetConfigs[bucketId] = { + data: [], + labels: [], + backgroundColor: [] + }; + } + const config = chartDatasetConfigs[bucketId]; + aggResp.buckets.forEach((bucket) => { + config.data.push(bucket.doc_count); + config.labels.push(bucket.key); + if (currDepth < maxAggDepth) { + const nextBucket = buckets[++currDepth]; + decodeBucket(nextBucket, bucket[nextBucket.id]); + currDepth--; + } + }); + } + decodeBucket(buckets[0], aggregations[buckets[0].id]); + const arrDatasets = buckets.map(bucket => { return chartDatasetConfigs[bucket.id]; }); + arrDatasets.forEach(set => { + const colorOffset = 10000; + let numDataPoints = set.data.length; + const maxColors = Math.pow(16,6) - colorOffset; + const difference = maxColors / numDataPoints; + let currColor = colorOffset; + while (numDataPoints-- > 0) { + set.backgroundColor.push(makeColor(currColor)); + currColor += difference; + } + }); + + return arrDatasets; + } + return function convertEsRespAndAggConfig(esResp, aggConfigs) { + const chartDatasetConfigs = []; + const aggConfigMap = aggConfigs.byId; + const decodedData = decodeBucketData(aggConfigs.bySchemaGroup.buckets, esResp.aggregations); + const flattenedLabels = _.reduce(decodedData, (prev, dataset) => { + return prev.concat(dataset.labels); + }, []); + if (myChart) { // Not a fan of this, i should be using update + myChart.destroy(); + } + myChart = new Chart($el, { + type: chartType, + data: { + labels: flattenedLabels, + datasets: decodedData + }, + options: { + tooltips: { + callbacks: { + title: function () { return 'hello world'; }, + label: function (item, data) { + const dataset = data.datasets[item.datasetIndex]; + return dataset.labels[item.index] + ': ' + dataset.data[item.index]; + } + } + } + } + }); + }; + } + return { restrict: 'E', scope : { @@ -29,6 +117,7 @@ uiModules }, template: visualizeTemplate, link: function ($scope, $el, attr) { + const esRespConvertor = esRespConvertorFactory($el.find('#canvas-chart'), $scope.vis.type.name); let chart; // set in "vis" watcher let minVisChartHeight = 180; @@ -148,6 +237,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; From 7dd90bd02660ddeae63296a9b04556741cb6fef3 Mon Sep 17 00:00:00 2001 From: Khalah Jones-Golden Date: Thu, 5 May 2016 17:01:48 -0400 Subject: [PATCH 2/4] [Chart] Trying to get the legend to work properly --- src/ui/public/visualize/visualize.html | 1 + src/ui/public/visualize/visualize.js | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/ui/public/visualize/visualize.html b/src/ui/public/visualize/visualize.html index 23ba006b61cce..9a19cbbdf527d 100644 --- a/src/ui/public/visualize/visualize.html +++ b/src/ui/public/visualize/visualize.html @@ -15,5 +15,6 @@

No results found

+
diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index 7dc36409bd8cc..22a246b1906e1 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -18,7 +18,7 @@ uiModules location: 'Visualize' }); - function esRespConvertorFactory($el, chartType) { + function esRespConvertorFactory($el, $legend, chartType) { chartType = { pie: 'pie', line: 'line', @@ -36,11 +36,14 @@ uiModules function decodeBucketData(buckets, aggregations) { const chartDatasetConfigs = {}; if (!buckets) { return chartDatasetConfigs; } + const maxAggDepth = buckets.length - 1; let currDepth = 0; + // meant to recursively crawl through es aggregations + // to make sense of the data for a charting library function decodeBucket(bucket, aggResp) { const bucketId = bucket.id; - if (!chartDatasetConfigs[bucket.id]) { + if (!chartDatasetConfigs[bucket.id]) { // add a empty dataset if we haven't chartDatasetConfigs[bucketId] = { data: [], labels: [], @@ -51,7 +54,7 @@ uiModules aggResp.buckets.forEach((bucket) => { config.data.push(bucket.doc_count); config.labels.push(bucket.key); - if (currDepth < maxAggDepth) { + if (currDepth < maxAggDepth) { // Crawl through the structure if we should const nextBucket = buckets[++currDepth]; decodeBucket(nextBucket, bucket[nextBucket.id]); currDepth--; @@ -60,15 +63,19 @@ uiModules } decodeBucket(buckets[0], aggregations[buckets[0].id]); const arrDatasets = buckets.map(bucket => { return chartDatasetConfigs[bucket.id]; }); + // Make some colors for all of the data points. arrDatasets.forEach(set => { const colorOffset = 10000; let numDataPoints = set.data.length; - const maxColors = Math.pow(16,6) - colorOffset; + const maxColors = Math.pow(16,6) - (colorOffset * 2); const difference = maxColors / numDataPoints; let currColor = colorOffset; - while (numDataPoints-- > 0) { + do { set.backgroundColor.push(makeColor(currColor)); currColor += difference; + } while (--numDataPoints > 0); + if (chartType !== 'pie') { + set.backgroundColor = set.backgroundColor[0]; } }); @@ -91,6 +98,9 @@ uiModules datasets: decodedData }, options: { + legend: { + display: false + }, tooltips: { callbacks: { title: function () { return 'hello world'; }, @@ -102,6 +112,8 @@ uiModules } } }); + + $legend.html(myChart.generateLegend()); }; } @@ -117,7 +129,7 @@ uiModules }, template: visualizeTemplate, link: function ($scope, $el, attr) { - const esRespConvertor = esRespConvertorFactory($el.find('#canvas-chart'), $scope.vis.type.name); + const esRespConvertor = esRespConvertorFactory($el.find('#canvas-chart'), $el.find('#chart-legend'), $scope.vis.type.name); let chart; // set in "vis" watcher let minVisChartHeight = 180; From 104e68de169e3a3a026d37ebb90b29e8c5884ccb Mon Sep 17 00:00:00 2001 From: Khalah Jones-Golden Date: Mon, 9 May 2016 09:41:53 -0400 Subject: [PATCH 3/4] Chart js work --- src/ui/public/vislib/styles/_legend.less | 4 ++++ src/ui/public/visualize/visualize.html | 8 +++++--- src/ui/public/visualize/visualize.js | 23 +++++++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) 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 9a19cbbdf527d..895457a142d21 100644 --- a/src/ui/public/visualize/visualize.html +++ b/src/ui/public/visualize/visualize.html @@ -13,8 +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 22a246b1906e1..7f7b01b0986b2 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -33,6 +33,7 @@ uiModules } return '#' + hexStr; } + const isPieChart = (chartType === 'pie'); function decodeBucketData(buckets, aggregations) { const chartDatasetConfigs = {}; if (!buckets) { return chartDatasetConfigs; } @@ -64,17 +65,21 @@ uiModules decodeBucket(buckets[0], aggregations[buckets[0].id]); const arrDatasets = buckets.map(bucket => { return chartDatasetConfigs[bucket.id]; }); // 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 colorOffset = 10000; + const allTheColors = Math.pow(16, 6); + const colorOffset = allTheColors / 8; let numDataPoints = set.data.length; - const maxColors = Math.pow(16,6) - (colorOffset * 2); + const maxColors = allTheColors - (colorOffset * 2); const difference = maxColors / numDataPoints; let currColor = colorOffset; - do { + while (numDataPoints-- > 0) { set.backgroundColor.push(makeColor(currColor)); currColor += difference; - } while (--numDataPoints > 0); - if (chartType !== 'pie') { + } + if (!isPieChart) { set.backgroundColor = set.backgroundColor[0]; } }); @@ -86,7 +91,7 @@ uiModules const aggConfigMap = aggConfigs.byId; const decodedData = decodeBucketData(aggConfigs.bySchemaGroup.buckets, esResp.aggregations); const flattenedLabels = _.reduce(decodedData, (prev, dataset) => { - return prev.concat(dataset.labels); + return _.uniq(prev.concat(dataset.labels)); }, []); if (myChart) { // Not a fan of this, i should be using update myChart.destroy(); @@ -98,6 +103,12 @@ uiModules datasets: decodedData }, options: { + legendCallback: function (chartObj) { + const multipleBuckets = aggConfigs.bySchemaGroup.buckets.length > 1; + const legendItems = multipleBuckets || isPieChart ? flattenedLabels : [aggConfigs.bySchemaGroup.metrics[0]._opts.type]; + const itemsHtmlArr = legendItems.map(item => { return '
  • ' + item + '
  • '; }); + return '
      ' + itemsHtmlArr.join('') + '
    '; + }, legend: { display: false }, From f1a0ce0a3c9592221dc51490a512457cc4ed0cf3 Mon Sep 17 00:00:00 2001 From: Khalah Jones Golden Date: Fri, 20 May 2016 08:58:31 -0400 Subject: [PATCH 4/4] [Visualize] Fixed legend and chart display for split lines --- package.json | 2 +- src/ui/public/visualize/visualize.js | 74 +++++++++++++++++----------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 2f76a0be0e506..b16b2761c7c5d 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "bootstrap": "3.3.6", "brace": "0.5.1", "bunyan": "1.7.1", - "chart.js": "2.1.0", + "chart.js": "2.1.3", "clipboard": "1.5.5", "commander": "2.8.1", "css-loader": "0.17.0", diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index 7f7b01b0986b2..a546400f2f823 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -34,36 +34,47 @@ uiModules return '#' + hexStr; } const isPieChart = (chartType === 'pie'); - function decodeBucketData(buckets, aggregations) { - const chartDatasetConfigs = {}; - if (!buckets) { return chartDatasetConfigs; } + function decodeBucketData(aggConfigs, aggregations) { + const datasetMap = {}; + const xAxisLabels = []; + // If there is no data + if (!aggConfigs) { return datasetMap; } - const maxAggDepth = buckets.length - 1; + 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(bucket, aggResp) { - const bucketId = bucket.id; - if (!chartDatasetConfigs[bucket.id]) { // add a empty dataset if we haven't - chartDatasetConfigs[bucketId] = { - data: [], - labels: [], - backgroundColor: [] - }; - } - const config = chartDatasetConfigs[bucketId]; + function decodeBucket(aggConfig, aggResp) { aggResp.buckets.forEach((bucket) => { - config.data.push(bucket.doc_count); - config.labels.push(bucket.key); + 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 nextBucket = buckets[++currDepth]; - decodeBucket(nextBucket, bucket[nextBucket.id]); + 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; } }); } - decodeBucket(buckets[0], aggregations[buckets[0].id]); - const arrDatasets = buckets.map(bucket => { return chartDatasetConfigs[bucket.id]; }); + 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 @@ -76,7 +87,9 @@ uiModules const difference = maxColors / numDataPoints; let currColor = colorOffset; while (numDataPoints-- > 0) { - set.backgroundColor.push(makeColor(currColor)); + if (isPieChart) { + set.backgroundColor.push(makeColor(currColor)); + } currColor += difference; } if (!isPieChart) { @@ -84,28 +97,29 @@ uiModules } }); - return arrDatasets; + return { + legend: legendLabels, + labels: xAxisLabels, + dataConfigs: arrDatasets + }; } return function convertEsRespAndAggConfig(esResp, aggConfigs) { - const chartDatasetConfigs = []; const aggConfigMap = aggConfigs.byId; const decodedData = decodeBucketData(aggConfigs.bySchemaGroup.buckets, esResp.aggregations); - const flattenedLabels = _.reduce(decodedData, (prev, dataset) => { - return _.uniq(prev.concat(dataset.labels)); - }, []); if (myChart) { // Not a fan of this, i should be using update myChart.destroy(); } myChart = new Chart($el, { type: chartType, data: { - labels: flattenedLabels, - datasets: decodedData + labels: decodedData.labels, + datasets: decodedData.dataConfigs }, + fill: false, options: { legendCallback: function (chartObj) { const multipleBuckets = aggConfigs.bySchemaGroup.buckets.length > 1; - const legendItems = multipleBuckets || isPieChart ? flattenedLabels : [aggConfigs.bySchemaGroup.metrics[0]._opts.type]; + const legendItems = multipleBuckets || isPieChart ? decodedData.legend : [aggConfigs.bySchemaGroup.metrics[0]._opts.type]; const itemsHtmlArr = legendItems.map(item => { return '
  • ' + item + '
  • '; }); return '
      ' + itemsHtmlArr.join('') + '
    '; }, @@ -117,7 +131,7 @@ uiModules title: function () { return 'hello world'; }, label: function (item, data) { const dataset = data.datasets[item.datasetIndex]; - return dataset.labels[item.index] + ': ' + dataset.data[item.index]; + return dataset.label + ': ' + dataset.data[item.index]; } } }