Skip to content

Commit

Permalink
Add Tag Cloud Visualization
Browse files Browse the repository at this point in the history
A tag cloud visualization is a visual representation of text data, typically used to visualize free form text. Tags are usually single words. The font size of word corresponds  with its importance.
  • Loading branch information
thomasneirynck authored Nov 21, 2016
1 parent 85913ab commit 31a6cba
Show file tree
Hide file tree
Showing 15 changed files with 834 additions and 13 deletions.
2 changes: 2 additions & 0 deletions docs/visualize.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,5 @@ include::visualize/pie.asciidoc[]
include::visualize/tilemap.asciidoc[]

include::visualize/vertbar.asciidoc[]

include::visualize/tagcloud.asciidoc[]
44 changes: 44 additions & 0 deletions docs/visualize/tagcloud.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[[tagcloud-chart]]
== Cloud Tag Charts

A tag cloud visualization is a visual representation of text data, typically used to visualize free form text.
Tags are usually single words, and the importance of each tag is shown with font size or color.

The font size for each word is determined by the _metrics_ aggregation. The following aggregations are available for
this chart:

include::y-axis-aggs.asciidoc[]


The _buckets_ aggregations determine what information is being retrieved from your data set.

Before you choose a buckets aggregation, select the *Split Tags* option.

You can specify the following bucket aggregations for tag cloud visualization:

*Terms*:: A {es-ref}search-aggregations-bucket-terms-aggregation.html[_terms_] aggregation enables you to specify the top
or bottom _n_ elements of a given field to display, ordered by count or a custom metric.

You can click the *Advanced* link to display more customization options for your metrics or bucket aggregation:

*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation
definition, as in the following example:

[source,shell]
{ "script" : "doc['grade'].value * 1.2" }

NOTE: In Elasticsearch releases 1.4.3 and later, this functionality requires you to enable
{es-ref}modules-scripting.html[dynamic Groovy scripting].


Select the *Options* tab to change the following aspects of the chart:

*Text Scale*:: You can select *linear*, *log*, or *square root* scales for the text scale. You can use a log
scale to display data that varies exponentially or a square root scale to
regularize the display of data sets with variabilities that are themselves highly variable.
*Orientation*:: You can select how to orientate your text in the tag cloud. You can choose one of the following options:
Single, right angles and multiple.
*Font Size*:: Allows you to set minimum and maximum font size to use for this visualization.


include::visualization-raw-data.asciidoc[]
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"commander": "2.8.1",
"css-loader": "0.17.0",
"d3": "3.5.6",
"d3-cloud": "1.2.1",
"dragula": "3.7.0",
"elasticsearch": "12.0.0-rc5",
"elasticsearch-browser": "12.0.0-rc5",
Expand Down Expand Up @@ -138,6 +139,7 @@
"mkdirp": "0.5.1",
"moment": "2.13.0",
"moment-timezone": "0.5.4",
"no-ui-slider": "1.2.0",
"node-fetch": "1.3.2",
"node-uuid": "1.4.7",
"pegjs": "0.9.0",
Expand Down
8 changes: 8 additions & 0 deletions src/core_plugins/tagcloud/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function (kibana) {

return new kibana.Plugin({
uiExports: {
visTypes: ['plugins/tagcloud/tag_cloud_vis']
}
});
};
4 changes: 4 additions & 0 deletions src/core_plugins/tagcloud/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "tagcloud",
"version": "kibana"
}
181 changes: 181 additions & 0 deletions src/core_plugins/tagcloud/public/__tests__/tag_cloud.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import expect from 'expect.js';
import _ from 'lodash';
import TagCloud from 'plugins/tagcloud/tag_cloud';

describe('tag cloud', function () {

let domNode;

beforeEach(function () {
domNode = document.createElement('div');
domNode.style.top = '0';
domNode.style.left = '0';
domNode.style.width = '512px';
domNode.style.height = '512px';
domNode.style.position = 'fixed';
domNode.style['pointer-events'] = 'none';
document.body.appendChild(domNode);
});

afterEach(function () {
document.body.removeChild(domNode);
});


const baseTestConfig = {
data: [
{text: 'foo', size: 1},
{text: 'bar', size: 5},
{text: 'foobar', size: 9},
],
options: {
orientation: 'single',
scale: 'linear',
minFontSize: 10,
maxFontSize: 36
},
expected: [
{
text: 'foo',
fontSize: '10px'
},
{
text: 'bar',
fontSize: '23px'
},
{
text: 'foobar',
fontSize: '36px'
}
]
};

const singleLayout = _.cloneDeep(baseTestConfig);
const rightAngleLayout = _.cloneDeep(baseTestConfig);
rightAngleLayout.options.orientation = 'right angled';
const multiLayout = _.cloneDeep(baseTestConfig);
multiLayout.options.orientation = 'multiple';
const logScale = _.cloneDeep(baseTestConfig);
logScale.options.scale = 'log';
logScale.expected[1].fontSize = '31px';
const sqrtScale = _.cloneDeep(baseTestConfig);
sqrtScale.options.scale = 'square root';
sqrtScale.expected[1].fontSize = '27px';
const biggerFont = _.cloneDeep(baseTestConfig);
biggerFont.options.minFontSize = 36;
biggerFont.options.maxFontSize = 72;
biggerFont.expected[0].fontSize = '36px';
biggerFont.expected[1].fontSize = '54px';
biggerFont.expected[2].fontSize = '72px';

[
singleLayout,
rightAngleLayout,
multiLayout,
logScale,
sqrtScale,
biggerFont
].forEach((test, index) => {

it(`should position elements correctly: ${index}`, done => {
const tagCloud = new TagCloud(domNode);
tagCloud.setData(test.data);
tagCloud.setOptions(test.options);
tagCloud.on('renderComplete', function onRender() {
tagCloud.removeListener('renderComplete', onRender);
const textElements = domNode.querySelectorAll('text');
verifyTagProperties(test.expected, textElements);
expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE);
done();
});
});
});

it(`should not put elements in view when container to small`, function (done) {

domNode.style.width = '1px';
domNode.style.height = '1px';

const tagCloud = new TagCloud(domNode);
tagCloud.setData(baseTestConfig.data);
tagCloud.setOptions(baseTestConfig.options);
tagCloud.on('renderComplete', function onRender() {
tagCloud.removeListener('renderComplete', onRender);
expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE);
const textElements = domNode.querySelectorAll('text');
for (let i = 0; i < textElements; i++) {
const bbox = textElements[i].getBoundingClientRect();
verifyBbox(bbox, false);
}
done();
});
});


it(`tags should fit after making container bigger`, function (done) {

domNode.style.width = '1px';
domNode.style.height = '1px';

const tagCloud = new TagCloud(domNode);
tagCloud.setData(baseTestConfig.data);
tagCloud.setOptions(baseTestConfig.options);
tagCloud.on('renderComplete', function onRender() {
tagCloud.removeListener('renderComplete', onRender);
expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE);

domNode.style.width = '512px';
domNode.style.height = '512px';
tagCloud.on('renderComplete', _ => {
expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE);
done();
});
tagCloud.resize();

});
});

it(`tags should no longer fit after making container smaller`, function (done) {

const tagCloud = new TagCloud(domNode);
tagCloud.setData(baseTestConfig.data);
tagCloud.setOptions(baseTestConfig.options);
tagCloud.on('renderComplete', function onRender() {
tagCloud.removeListener('renderComplete', onRender);
expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.COMPLETE);

domNode.style.width = '1px';
domNode.style.height = '1px';
tagCloud.on('renderComplete', _ => {
expect(tagCloud.getStatus()).to.equal(TagCloud.STATUS.INCOMPLETE);
done();
});
tagCloud.resize();

});

});

function verifyTagProperties(expectedValues, actualElements) {
expect(actualElements.length).to.equal(expectedValues.length);
expectedValues.forEach((test, index) => {
expect(actualElements[index].style.fontSize).to.equal(test.fontSize);
expect(actualElements[index].innerHTML).to.equal(test.text);
isInsideContainer(actualElements[index]);
});
}

function isInsideContainer(actualElement) {
const bbox = actualElement.getBoundingClientRect();
verifyBbox(bbox, true);
}

function verifyBbox(bbox, shouldBeInside) {
expect(bbox.top >= 0 && bbox.top <= domNode.offsetHeight).to.be(shouldBeInside);
expect(bbox.bottom >= 0 && bbox.bottom <= domNode.offsetHeight).to.be(shouldBeInside);
expect(bbox.left >= 0 && bbox.left <= domNode.offsetWidth).to.be(shouldBeInside);
expect(bbox.right >= 0 && bbox.right <= domNode.offsetWidth).to.be(shouldBeInside);
}


});
Loading

0 comments on commit 31a6cba

Please sign in to comment.