├── .node-version
├── img
├── kibana.png
├── radar.png
├── heroes-vis.png
└── heroes-params.png
├── public
├── kibi_radar_vis.html
├── __tests__
│ └── index.js
├── kibi_radar_vis.less
├── kibi_radar_vis_params.html
├── kibi_radar_vis.js
└── kibi_radar_vis_controller.js
├── .gitignore
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── utils
│ ├── loadPackageJson.gradle
│ └── repository.gradle
├── gradle.properties
├── index.js
├── .editorconfig
├── LICENSE.md
├── package.json
├── .eslintrc
├── gulpfile.js
├── README.md
└── gradlew
/.node-version:
--------------------------------------------------------------------------------
1 | 8.11.4
2 |
--------------------------------------------------------------------------------
/img/kibana.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sirensolutions/kibi_radar_vis/HEAD/img/kibana.png
--------------------------------------------------------------------------------
/img/radar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sirensolutions/kibi_radar_vis/HEAD/img/radar.png
--------------------------------------------------------------------------------
/public/kibi_radar_vis.html:
--------------------------------------------------------------------------------
1 |
"
9 | ],
10 | "description": "Kibi Radar Chart plugin",
11 | "main": "index.js",
12 | "license": "Apache-2.0",
13 | "homepage": "http://siren.solutions/kibi",
14 | "repository": {
15 | "type": "git",
16 | "url": "git@github.com:sirensolutions/kibi-radar-chart-plugin.git"
17 | },
18 | "scripts": {
19 | "test": "gulp test",
20 | "test:coverage": "gulp coverage",
21 | "start": "gulp dev --kibanahomepath=../kibana",
22 | "precommit": "gulp lint",
23 | "build": "gulp build"
24 | },
25 | "devDependencies": {
26 | "eslint-plugin-mocha": "1.0.0",
27 | "babel-eslint": "^4.1.6",
28 | "bluebird": "3.5.1",
29 | "eslint": "^1.10.3",
30 | "gulp": "3.9.1",
31 | "gulp-eslint": "^1.1.1",
32 | "gulp-zip": "4.0.0",
33 | "gulp-util": "^3.0.7",
34 | "husky": "0.14.3",
35 | "lodash": "^3.10.1",
36 | "mkdirp": "^0.5.1",
37 | "rimraf": "2.6.2",
38 | "rsync": "0.6.1",
39 | "minimist": "1.2.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | ---
2 | parser: babel-eslint
3 |
4 | plugins:
5 | - mocha
6 |
7 | env:
8 | es6: true
9 | amd: true
10 | node: true
11 | mocha: true
12 | browser: true
13 |
14 |
15 | rules:
16 | block-scoped-var: 2
17 | camelcase: [ 2, { properties: never } ]
18 | comma-dangle: 0
19 | comma-style: [ 2, last ]
20 | consistent-return: 0
21 | curly: [ 2, multi-line ]
22 | dot-location: [ 2, property ]
23 | dot-notation: [ 2, { allowKeywords: true } ]
24 | eqeqeq: [ 2, allow-null ]
25 | guard-for-in: 2
26 | indent: [ 2, 2, { SwitchCase: 1 } ]
27 | key-spacing: [ 0, { align: value } ]
28 | max-len: [ 2, 140, 2, { ignoreComments: true, ignoreUrls: true } ]
29 | new-cap: [ 2, { capIsNewExceptions: [ Private ] } ]
30 | no-bitwise: 0
31 | no-caller: 2
32 | no-cond-assign: 0
33 | no-const-assign: 2
34 | no-debugger: 2
35 | no-empty: 2
36 | no-eval: 2
37 | no-extend-native: 2
38 | no-extra-parens: 0
39 | no-irregular-whitespace: 2
40 | no-iterator: 2
41 | no-loop-func: 2
42 | no-multi-spaces: 0
43 | no-multi-str: 2
44 | no-nested-ternary: 2
45 | no-new: 0
46 | no-path-concat: 0
47 | no-proto: 2
48 | no-return-assign: 0
49 | no-script-url: 2
50 | no-sequences: 2
51 | no-shadow: 0
52 | no-trailing-spaces: 2
53 | no-undef: 2
54 | no-underscore-dangle: 0
55 | no-unused-expressions: 0
56 | no-unused-vars: 0
57 | no-use-before-define: [ 2, nofunc ]
58 | no-var: 1
59 | no-with: 2
60 | one-var: [ 2, never ]
61 | prefer-const: 1
62 | quotes: [ 2, single ]
63 | semi-spacing: [ 2, { before: false, after: true } ]
64 | semi: [ 2, always ]
65 | space-after-keywords: [ 2, always ]
66 | space-before-blocks: [ 2, always ]
67 | space-before-function-paren: [ 2, { anonymous: always, named: never } ]
68 | space-in-parens: [ 2, never ]
69 | space-infix-ops: [ 2, { int32Hint: false } ]
70 | space-return-throw-case: [ 2 ]
71 | space-unary-ops: [ 2 ]
72 | strict: [ 2, never ]
73 | valid-typeof: 2
74 | wrap-iife: [ 2, outside ]
75 | yoda: 0
76 |
77 | mocha/no-exclusive-tests: 2
78 | mocha/handle-done-callback: 2
79 |
--------------------------------------------------------------------------------
/public/kibi_radar_vis_params.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
26 |
27 |
28 |
32 |
33 |
34 |
38 |
39 |
40 |
44 |
45 |
46 |
50 |
51 |
52 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/public/kibi_radar_vis.js:
--------------------------------------------------------------------------------
1 | import { VisVisTypeProvider } from 'ui/vis/vis_type';
2 | import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type';
3 | import { VisSchemasProvider } from 'ui/vis/schemas';
4 | import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
5 |
6 | // we need to load the css ourselves
7 | import 'plugins/kibi_radar_vis/kibi_radar_vis.less';
8 | // we also need to load the controller and used by the template
9 | import 'plugins/kibi_radar_vis/kibi_radar_vis_controller';
10 | import template from 'plugins/kibi_radar_vis/kibi_radar_vis.html';
11 | // register the provider with the visTypes registry
12 | VisTypesRegistryProvider.register(RadarVisProvider);
13 |
14 | function RadarVisProvider(Private) {
15 | const VisType = Private(VisVisTypeProvider);
16 | const TemplateVisType = Private(TemplateVisTypeProvider);
17 | const Schemas = Private(VisSchemasProvider);
18 |
19 | // return the visType object, which kibana will use to display and configure new
20 | // Vis object of this type.
21 | return new TemplateVisType({
22 | name: 'radar',
23 | title: 'Kibi Radar Chart',
24 | description: 'A radar chart is a graphical method of displaying multivariate data ' +
25 | 'in the form of a two-dimensional chart of three or more ' +
26 | 'quantitative variables represented on axes starting from the same point.' +
27 | ' The relative position and angle of the axes is typically uninformative.',
28 | icon: 'fa-snowflake',
29 | category: VisType.CATEGORY.KIBI,
30 | template,
31 | params: {
32 | defaults: {
33 | fontSize: 60,
34 | shareYAxis: true,
35 | addTooltip: true,
36 | addLegend: true,
37 | isDonut: false,
38 | isFacet: false,
39 | addLevel: true,
40 | addAxe: true,
41 | addVertice: true,
42 | addPolygon: true,
43 | addLevelLabel: true,
44 | addAxeLabel: true,
45 | addLevelScale: 1,
46 | addLabelScale: 0.9,
47 | addLevelNumber: 5
48 | },
49 | editor: require('plugins/kibi_radar_vis/kibi_radar_vis_params.html')
50 | },
51 | schemas: new Schemas([
52 | {
53 | group: 'metrics',
54 | name: 'metric',
55 | title: 'Metric',
56 | min: 3,
57 | defaults: [
58 | { type: 'count', schema: 'metric' },
59 | { type: 'count', schema: 'metric' },
60 | { type: 'count', schema: 'metric' }
61 | ],
62 | aggFilter: ['count','cardinality', 'avg', 'sum', 'min', 'max']
63 | },
64 | {
65 | group: 'buckets',
66 | name: 'segment',
67 | icon: 'fa fa-scissors',
68 | title: 'Split Slices',
69 | min: 1,
70 | max: 1,
71 | aggFilter: ['terms','range']
72 | }
73 | ])
74 | });
75 | }
76 |
77 | // export the provider so that the visType can be required with Private()
78 | export default RadarVisProvider;
79 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var _ = require('lodash');
3 | var path = require('path');
4 | var mkdirp = require('mkdirp');
5 | var Rsync = require('rsync');
6 | var Promise = require('bluebird');
7 | var eslint = require('gulp-eslint');
8 | var rimraf = require('rimraf');
9 | var zip = require('gulp-zip');
10 | var fs = require('fs');
11 | var spawn = require('child_process').spawn;
12 | var minimist = require('minimist');
13 |
14 | var pkg = require('./package.json');
15 | var packageName = pkg.name;
16 |
17 | var buildDir = path.resolve(__dirname, 'build/gulp');
18 | var targetDir = path.resolve(__dirname, 'target/gulp');
19 | var buildTarget = path.resolve(buildDir, 'kibana', packageName);
20 |
21 | var include = [
22 | 'package.json',
23 | 'index.js',
24 | 'public'
25 | ];
26 |
27 | var knownOptions = {
28 | string: 'kibanahomepath',
29 | default: { kibanahomepath: '../kibi-internal' }
30 | };
31 | var options = minimist(process.argv.slice(2), knownOptions);
32 |
33 | var kibanaPluginDir = path.resolve(__dirname, options.kibanahomepath + '/siren_plugins/' + packageName);
34 |
35 | function syncPluginTo(dest, done) {
36 | mkdirp(dest, function (err) {
37 | if (err) return done(err);
38 | Promise.all(include.map(function (name) {
39 | var source = path.resolve(__dirname, name);
40 | return new Promise(function (resolve, reject) {
41 | var rsync = new Rsync();
42 | rsync
43 | .source(source)
44 | .destination(dest)
45 | .flags('uav')
46 | .recursive(true)
47 | .set('delete')
48 | .output(function (data) {
49 | process.stdout.write(data.toString('utf8'));
50 | });
51 | rsync.execute(function (err) {
52 | if (err) {
53 | console.log(err);
54 | return reject(err);
55 | }
56 | resolve();
57 | });
58 | });
59 | }))
60 | .then(function () {
61 | return new Promise(function (resolve, reject) {
62 | mkdirp(path.join(buildTarget, 'node_modules'), function (err) {
63 | if (err) return reject(err);
64 | resolve();
65 | });
66 | });
67 | })
68 | .then(function () {
69 | spawn('npm', ['install', '--production'], {
70 | cwd: dest,
71 | stdio: 'inherit'
72 | })
73 | .on('close', done);
74 | })
75 | .catch(done);
76 | });
77 | }
78 |
79 | gulp.task('sync', function (done) {
80 | syncPluginTo(kibanaPluginDir, done);
81 | });
82 |
83 | gulp.task('lint', function (done) {
84 | return gulp.src([
85 | 'server/**/*.js',
86 | 'public/**/*.js',
87 | '!public/webpackShims/**'
88 | ]).pipe(eslint())
89 | .pipe(eslint.formatEach())
90 | .pipe(eslint.failOnError());
91 | });
92 |
93 | gulp.task('clean', function (done) {
94 | Promise.each([buildDir, targetDir], function (dir) {
95 | return new Promise(function (resolve, reject) {
96 | rimraf(dir, function (err) {
97 | if (err) return reject(err);
98 | resolve();
99 | });
100 | });
101 | }).nodeify(done);
102 | });
103 |
104 | gulp.task('build', ['clean'], function (done) {
105 | syncPluginTo(buildTarget, done);
106 | });
107 |
108 | gulp.task('package', ['build'], function (done) {
109 | return gulp.src(path.join(buildDir, '**', '*'))
110 | .pipe(zip(packageName + '.zip'))
111 | .pipe(gulp.dest(targetDir));
112 | });
113 |
114 | gulp.task('dev', ['sync'], function (done) {
115 | gulp.watch([
116 | 'package.json',
117 | 'index.js',
118 | 'public/**/*',
119 | 'server/**/*'
120 | ], ['sync', 'lint']);
121 | });
122 |
123 | gulp.task('test', ['sync'], function(done) {
124 | spawn('grunt', ['test:browser', '--grep=Kibi Radar Chart'], {
125 | cwd: options.kibanahomepath,
126 | stdio: 'inherit'
127 | }).on('close', done);
128 | });
129 |
130 | gulp.task('coverage', ['sync'], function(done) {
131 | spawn('grunt', ['test:coverage', '--grep=Kibi Radar Chart'], {
132 | cwd: options.kibanahomepath,
133 | stdio: 'inherit'
134 | }).on('close', done);
135 | });
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kibi/Kibana Radar Chart Plugin
2 |
3 | This is a plugin for [Kibana](https://www.elastic.co/products/kibana) and [Kibi](http://siren.solutions/kibi) (our [extention of Kibana for Relational Data](https://www.linkedin.com/pulse/extending-elasticsearch-kibana-do-data-intelligence-kibi-tummarello))
4 |
5 | A radar chart is a graphical method of displaying multivariate data in the form of a two-dimensional chart of three or more quantitative variables represented on axes starting from the same point. The relative position and angle of the axes is typically uninformative.
6 |
7 | 
8 | 
9 |
10 |
11 | ## Compatibility
12 |
13 | This plugin can be installed in both:
14 |
15 | * [Kibana: 4.3+](https://www.elastic.co/downloads/past-releases/kibana-4-3-0)
16 | * [Kibi: 0.3+](https://siren.solutions/kibi)
17 |
18 | The following table shows the compatibility between releases of Kibi/Kibana and Radar Chart Plugin plugin
19 |
20 | Kibi/Kibana|Radar Chart Plugin
21 | -----|-----
22 | 5.6.4|master
23 | 5.2.2|branch-5.2.2
24 | 5.2.1|branch-5.2.1
25 | 5.2.0|branch-5.2.0
26 | 5.1.2|branch-5.1.2
27 | 5.1.1|branch-5.1.1
28 | 5.0.2|branch-5.0.2
29 | 5.0.1|branch-5.0.1
30 | 5.0.0|branch-5.0.0
31 | 4.6.4|branch-4.6.4
32 | 4.6.3|4.5.3
33 | 4.5.x|4.5.3
34 | 4.4.x|4.4.2
35 | 4.3.x|0.1.0
36 |
37 | ## Installation
38 |
39 | ### Automatic
40 |
41 | For kibi/kibana 4.x
42 |
43 | ```sh
44 | $ # for kibi
45 | $ ./bin/kibi plugin -i kibi_radar_vis -u https://github.com/sirensolutions/kibi_radar_vis/archive/4.5.3.zip
46 | $ # for kibana
47 | $ ./bin/kibana plugin -i kibi_radar_vis -u https://github.com/sirensolutions/kibi_radar_vis/archive/4.5.3.zip
48 | ```
49 |
50 | For kibi/kibana 5.x.x (replace the 5.x.x with correct version number)
51 |
52 | ```sh
53 | $ # for kibi
54 | $ ./bin/kibi-plugin install https://github.com/sirensolutions/kibi_radar_vis/releases/download/5.x.x/kibi_radar_vis-5.x.x.zip
55 | $ # for kibana
56 | $ ./bin/kibana-plugin install https://github.com/sirensolutions/kibi_radar_vis/releases/download/5.x.x/kibi_radar_vis-5.x.x.zip
57 | ```
58 |
59 | ### Manual
60 |
61 | For kibi/kibana 4.x
62 |
63 | ```sh
64 | $ git clone https://github.com/sirensolutions/kibi_radar_vis.git
65 | $ cd kibi_radar_vis
66 | $ npm install
67 | $ npm run build
68 | $ cp -R build/kibi_radar_vis KIBANA_FOLDER_PATH/installedPlugins/
69 | ```
70 |
71 | For kibi/kibana 5.x
72 |
73 | ```sh
74 | $ git clone https://github.com/sirensolutions/kibi_radar_vis.git
75 | $ cd kibi_radar_vis
76 | $ git checkout branch-5.x.x
77 | $ npm install
78 | $ npm run build
79 | $ cp -R build/kibana/kibi_radar_vis KIBANA_FOLDER_PATH/plugins/
80 | ```
81 |
82 | ## Uninstall
83 |
84 | For kibi/kibana 4.x
85 |
86 | ```sh
87 | $ # for kibi
88 | $ bin/kibi plugin --remove kibi_radar_vis
89 | $ # for kibana
90 | $ bin/kibana plugin --remove kibi_radar_vis
91 | ```
92 |
93 | For kibi/kibana 5.x
94 |
95 | ```sh
96 | $ # for kibi
97 | $ bin/kibi-plugin remove kibi_radar_vis
98 | $ # for kibana
99 | $ bin/kibana-plugin remove kibi_radar_vis
100 | ```
101 |
102 | ## Development
103 |
104 | - Clone the repository at the same level as you've cloned Kibana (>=4.6.4)
105 | - Switch to the same node version as Kibana using nvm
106 | (currently `nvm use 6.9.0`)
107 | - Install dependencies with `npm install`
108 | - Install the plugin to Kibana and start watching for changes by running
109 | `npm start`
110 |
111 | ## Demo
112 |
113 | 
114 |
115 | Create a sample index with the commands below and then create a new radar visualization.
116 |
117 | ```sh
118 | $ curl -XPUT 'http://localhost:9200/hero' -d '
119 | {
120 | "mappings": {
121 | "Hero": {
122 | "properties": {
123 | "name": {
124 | "type": "string",
125 | "index": "not_analyzed"
126 | }
127 | }
128 | }
129 | }
130 | }
131 | '
132 |
133 | $ curl 'http://localhost:9200/hero/Hero' -d '
134 | {
135 | "name": "Thor",
136 | "intelligence": 2,
137 | "strength": 7,
138 | "speed": 7,
139 | "durability": 6,
140 | "energy": 6,
141 | "fighting": 4,
142 | "description": "god-like durability"
143 | }
144 | '
145 |
146 | $ curl 'http://localhost:9200/hero/Hero' -d '
147 | {
148 | "name": "Iron Man",
149 | "intelligence": 6,
150 | "strength": 6,
151 | "speed": 5,
152 | "durability": 6,
153 | "energy": 6,
154 | "fighting": 4,
155 | "description": "smart entreprenuer"
156 | }
157 | '
158 |
159 | $ curl 'http://localhost:9200/hero/Hero' -d '
160 | {
161 | "name": "Captain America",
162 | "intelligence": 3,
163 | "strength": 3,
164 | "speed": 2,
165 | "durability": 3,
166 | "energy": 1,
167 | "fighting": 6,
168 | "description": "only human"
169 | }
170 | '
171 |
172 | $ curl 'http://localhost:9200/hero/Hero' -d '
173 | {
174 | "name": "Hulk",
175 | "intelligence": 6,
176 | "strength": 7,
177 | "speed": 3,
178 | "durability": 7,
179 | "energy": 1,
180 | "fighting": 4,
181 | "description": "brilliant scientist"
182 | }
183 | '
184 | ```
185 |
186 | The metrics define the dimensions of the chart, and should be at least three.
187 | Each colored area is defined in the bucket section, e.g., a hero's name.
188 |
189 | 
190 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/public/kibi_radar_vis_controller.js:
--------------------------------------------------------------------------------
1 | import d3 from 'd3';
2 | import { AggResponseTabifyProvider } from 'ui/agg_response/tabify/tabify';
3 | import { uiModules } from 'ui/modules';
4 | // get the kibana/metric_vis module, and make sure that it requires the 'kibana' module if it
5 | // didn't already
6 | const module = uiModules.get('kibana/kibi_radar_vis', ['kibana']);
7 |
8 | module.controller('KbnRadarVisController', function ($scope, $element, $rootScope, Private) {
9 | const tabifyAggResponse = Private(AggResponseTabifyProvider);
10 |
11 | let data;
12 | let config;
13 | let chartVis;
14 | let width;
15 | let height;
16 | // declare data
17 | let tableGroups = null;
18 |
19 | //mouse events
20 | const over = 'ontouchstart' in window ? 'touchstart' : 'mouseover';
21 | const out = 'ontouchstart' in window ? 'touchend' : 'mouseout';
22 | const svgRoot = $element[0];
23 |
24 | // set default config
25 | const _initConfig = function () {
26 | const chartW = width / 3;
27 | const chartH = width / 3;
28 | config = {
29 | w: chartW,
30 | h: chartH,
31 | facet: false,
32 | levels: 5,
33 | levelScale: 0.85,
34 | labelScale: 1.0,
35 | facetPaddingScale: 1.0,
36 | maxValue: 0,
37 | radians: 2 * Math.PI,
38 | polygonAreaOpacity: 0.3,
39 | polygonStrokeOpacity: 1,
40 | polygonPointSize: 4,
41 | legendBoxSize: 10,
42 | translateX: chartW / 8,
43 | translateY: chartH / 8,
44 | paddingX: chartW,
45 | paddingY: chartH,
46 | colors: d3.scale.category10(),
47 | showLevels: true,
48 | showLevelsLabels: true,
49 | showAxesLabels: true,
50 | showAxes: true,
51 | showLegend: true,
52 | showVertices: true,
53 | showPolygons: true
54 | };
55 |
56 | // initiate main vis component
57 | chartVis = {
58 | svg: null,
59 | tooltip: null,
60 | levels: null,
61 | axis: null,
62 | vertices: null,
63 | legend: null,
64 | allAxis: null,
65 | total: null,
66 | radius: null
67 | };
68 | };
69 |
70 |
71 | // adjust config parameters
72 | const _updateConfig = function () {
73 | config.maxValue = Math.max(config.maxValue, d3.max(data, function (d) {
74 | return d3.max(d.axes, function (o) { return o.value; });
75 | }));
76 | config.levelScale = $scope.vis.params.addLevelScale;
77 | config.labelScale = $scope.vis.params.addLabelScale;
78 | config.levels = $scope.vis.params.addLevelNumber;
79 | config.w *= config.levelScale;
80 | config.h *= config.levelScale;
81 | config.paddingX = config.w * config.levelScale;
82 | config.paddingY = config.h * config.levelScale;
83 | config.facet = $scope.vis.params.isFacet;
84 |
85 | // if facet required:
86 | if (config.facet) {
87 | width /= data.length;
88 | height /= data.length;
89 | config.w /= data.length;
90 | config.h /= data.length;
91 | config.paddingX /= (data.length / config.facetPaddingScale);
92 | config.paddingY /= (data.length / config.facetPaddingScale);
93 | config.polygonPointSize *= Math.pow(0.9, data.length);
94 | }
95 | };
96 |
97 |
98 | const _updateDimensions = function () {
99 | const delta = 18;
100 | let w = $element.parent().width();
101 | let h = $element.parent().height();
102 | if (w) {
103 | if (w > delta) {
104 | w -= delta;
105 | }
106 | width = w;
107 | }
108 | if (h) {
109 | if (h > delta) {
110 | h -= delta;
111 | }
112 | height = h;
113 | }
114 | };
115 |
116 | const _buildCoordinates = function (data) {
117 | data.forEach(function (group) {
118 | group.axes.forEach(function (d, i) {
119 | const sin = Math.sin(i * config.radians / chartVis.totalAxes);
120 | const cos = Math.cos(i * config.radians / chartVis.totalAxes);
121 | d.coordinates = { // [x, y] coordinates
122 | x: config.w / 2 * (1 - (parseFloat(Math.max(d.value, 0)) / config.maxValue) * sin),
123 | y: config.h / 2 * (1 - (parseFloat(Math.max(d.value, 0)) / config.maxValue) * cos)
124 | };
125 | });
126 | });
127 | };
128 |
129 | const _buildVisComponents = function () {
130 | // update vis parameters
131 | chartVis.allAxis = data[0].axes.map(function (i, j) { return i.axis; });
132 | chartVis.totalAxes = chartVis.allAxis.length;
133 | chartVis.radius = Math.min(config.w / 2, config.h / 2);
134 | const div = d3.select(svgRoot)
135 | .append('svg')
136 | .attr('width', width)
137 | .attr('height', height);
138 |
139 | //create main chartVis svg
140 | if (config.facet) {
141 | chartVis.svg = div
142 | .append('svg').classed('svg-vis', true)
143 | .attr('width', config.w + config.paddingX)
144 | .attr('height', config.h + config.paddingY)
145 | .append('svg:g')
146 | .attr('transform', 'translate(' + config.translateX + ',' + config.translateY + ')');
147 | } else {
148 | chartVis.svg = div.append('svg')
149 | .attr('width', width)
150 | .attr('height', height)
151 | .append('svg:g')
152 | .attr('transform', 'translate(' + width / 4 + ',' + height / 4 + ')');
153 | }
154 |
155 | // create verticesTooltip
156 | chartVis.verticesTooltip = d3.select('body')
157 | .append('div').classed('verticesTooltip', true)
158 | .attr('opacity', 0)
159 | .style({
160 | 'position': 'absolute',
161 | 'color': 'black',
162 | 'font-size': '10px',
163 | 'width': '100px',
164 | 'height': 'auto',
165 | 'padding': '5px',
166 | 'border': '2px solid gray',
167 | 'border-radius': '5px',
168 | 'pointer-events': 'none',
169 | 'opacity': '0',
170 | 'background': '#f4f4f4'
171 | });
172 |
173 | // create levels
174 | chartVis.levels = chartVis.svg.selectAll('.levels')
175 | .append('svg:g').classed('levels', true);
176 |
177 | // create axes
178 | chartVis.axes = chartVis.svg.selectAll('.axes')
179 | .append('svg:g').classed('axes', true);
180 |
181 | // create vertices
182 | chartVis.vertices = chartVis.svg.selectAll('.vertices');
183 |
184 | //Initiate Legend
185 | chartVis.legend = chartVis.svg.append('svg:g').classed('legend', true)
186 | .attr('height', config.h / 2)
187 | .attr('width', config.w / 2)
188 | .attr('transform', 'translate(' + 0 + ', ' + 1.1 * config.h + ')');
189 | };
190 |
191 | const _buildSingleLevelLine = function (levelFactor) {
192 | chartVis.levels
193 | .data(chartVis.allAxis).enter()
194 | .append('svg:line').classed('level-lines', true)
195 | .attr('x1', function (d, i) { return levelFactor * (1 - Math.sin(i * config.radians / chartVis.totalAxes)); })
196 | .attr('y1', function (d, i) { return levelFactor * (1 - Math.cos(i * config.radians / chartVis.totalAxes)); })
197 | .attr('x2', function (d, i) { return levelFactor * (1 - Math.sin((i + 1) * config.radians / chartVis.totalAxes)); })
198 | .attr('y2', function (d, i) { return levelFactor * (1 - Math.cos((i + 1) * config.radians / chartVis.totalAxes)); })
199 | .attr('transform', 'translate(' + (config.w / 2 - levelFactor) + ', ' + (config.h / 2 - levelFactor) + ')')
200 | .attr('stroke', 'gray')
201 | .attr('stroke-width', '0.5px');
202 | };
203 |
204 | // builds out the levels of the radar chart
205 | const _buildLevels = function () {
206 | for (let level = 0; level < config.levels; level++) {
207 | const levelFactor = chartVis.radius * ((level + 1) / config.levels);
208 | // build level-lines
209 | _buildSingleLevelLine(levelFactor);
210 | }
211 | };
212 |
213 | const _buildSingleLevelLabel = function (level, levelFactor) {
214 | chartVis.levels
215 | .data([1]).enter()
216 | .append('svg:text').classed('level-labels', true)
217 | .text((config.maxValue * (level + 1) / config.levels).toFixed(2))
218 | .attr('x', function (d) { return levelFactor * (1 - Math.sin(0)); })
219 | .attr('y', function (d) { return levelFactor * (1 - Math.cos(0)); })
220 | .attr('transform', 'translate(' + (config.w / 2 - levelFactor + 5) + ', ' + (config.h / 2 - levelFactor) + ')')
221 | .attr('fill', 'gray')
222 | .attr('font-family', 'sans-serif')
223 | .attr('font-size', 10 * config.labelScale + 'px');
224 | };
225 |
226 | // builds out the levels labels
227 | const _buildLevelsLabels = function () {
228 | for (let level = 0; level < config.levels; level++) {
229 | const levelFactor = chartVis.radius * ((level + 1) / config.levels);
230 | // build level-labels
231 | _buildSingleLevelLabel(level, levelFactor);
232 | }
233 | };
234 |
235 |
236 | // builds out the axes
237 | const _buildAxes = function () {
238 | chartVis.axes
239 | .data(chartVis.allAxis).enter()
240 | .append('svg:line').classed('axis-lines', true)
241 | .attr('x1', config.w / 2)
242 | .attr('y1', config.h / 2)
243 | .attr('x2', function (d, i) { return config.w / 2 * (1 - Math.sin(i * config.radians / chartVis.totalAxes)); })
244 | .attr('y2', function (d, i) { return config.h / 2 * (1 - Math.cos(i * config.radians / chartVis.totalAxes)); })
245 | .attr('stroke', 'grey')
246 | .attr('stroke-width', '1px');
247 | };
248 | // builds out the axes labels
249 | const _buildAxesLabels = function () {
250 | chartVis.axes
251 | .data(chartVis.allAxis).enter()
252 | .append('svg:text').classed('axis-labels', true)
253 | .text(function (d) { return d; })
254 | .attr('text-anchor', 'middle')
255 | .attr('x', function (d, i) { return config.w / 2 * (1 - 1.3 * Math.sin(i * config.radians / chartVis.totalAxes)); })
256 | .attr('y', function (d, i) { return config.h / 2 * (1 - 1.1 * Math.cos(i * config.radians / chartVis.totalAxes)); })
257 | .attr('font-family', 'sans-serif')
258 | .attr('font-size', 11 * config.labelScale + 'px');
259 | };
260 |
261 | // builds out the legend
262 | const _buildLegend = function (data) {
263 | //Create legend squares
264 | if (config.facet) {
265 | chartVis.legend.selectAll('.legend-tiles')
266 | .data(data).enter()
267 | .append('svg:rect').classed('legend-tiles', true)
268 | .attr('x', config.w - config.paddingX / 2)
269 | .attr('y', function (d, i) { return i * 2 * config.legendBoxSize; })
270 | .attr('width', config.legendBoxSize)
271 | .attr('height', config.legendBoxSize)
272 | .attr('fill', function (d, g) { return config.colors(g); });
273 |
274 | //Create text next to squares
275 | chartVis.legend.selectAll('.legend-labels')
276 | .data(data).enter()
277 | .append('svg:text').classed('legend-labels', true)
278 | .attr('x', config.w - config.paddingX / 2 + (1.5 * config.legendBoxSize))
279 | .attr('y', function (d, i) { return i * 2 * config.legendBoxSize; })
280 | .attr('dy', 0.07 * config.legendBoxSize + 'em')
281 | .attr('font-size', 11 * config.labelScale + 'px')
282 | .attr('fill', 'gray')
283 | .text(function (d) {
284 | return d.group;
285 | });
286 | } else {
287 | chartVis.legend.selectAll('.legend-tiles')
288 | .data(data).enter()
289 | .append('svg:rect').classed('legend-tiles', true)
290 | .attr('x', config.w + config.w / 4)
291 | .attr('y', function (d, i) { return i * 2 * config.legendBoxSize - config.h / 2; })
292 | .attr('width', config.legendBoxSize)
293 | .attr('height', config.legendBoxSize)
294 | .on(over, function (d,i) {
295 | chartVis.svg.selectAll('.polygon-areas') // fade all other polygons out
296 | .transition(250)
297 | .attr('fill-opacity', 0.1)
298 | .attr('stroke-opacity', 0.1);
299 | d3.select('#polygon_' + i) // focus on active polygon
300 | .transition(250)
301 | .attr('fill-opacity', 0.7)
302 | .attr('stroke-opacity', config.polygonStrokeOpacity);
303 | })
304 | .on(out, function () {
305 | d3.selectAll('.polygon-areas')
306 | .transition(250)
307 | .attr('fill-opacity', config.polygonAreaOpacity)
308 | .attr('stroke-opacity', 1);
309 | })
310 | .attr('fill', function (d, g) { return config.colors(g); });
311 |
312 | //Create text next to squares
313 | chartVis.legend.selectAll('.legend-labels')
314 | .data(data).enter()
315 | .append('svg:text').classed('legend-labels', true)
316 | .attr('x', config.w + config.w / 4 + (1.5 * config.legendBoxSize))
317 | .attr('y', function (d, i) { return i * 2 * config.legendBoxSize - config.h / 2; })
318 | .attr('dy', 0.07 * config.legendBoxSize + 'em')
319 | .attr('font-size', 11 * config.labelScale + 'px')
320 | .attr('fill', 'gray')
321 | .text(function (d) {
322 | return d.group;
323 | });
324 | }
325 | };
326 |
327 | // show tooltip of vertices
328 | const _verticesTooltipShow = function (d) {
329 | chartVis.verticesTooltip.style('opacity', 0.9)
330 | .html('Value: ' + d.value + '
')
331 | .style('left', (d3.event.pageX) + 'px')
332 | .style('top', (d3.event.pageY) + 'px');
333 | };
334 |
335 | // hide tooltip of vertices
336 | const _verticesTooltipHide = function () {
337 | chartVis.verticesTooltip.style('opacity', 0);
338 | };
339 |
340 | // builds out the polygon vertices of the dataset
341 | const _buildVertices = function (data) {
342 | data.forEach(function (group, g) {
343 | chartVis.vertices
344 | .data(group.axes).enter()
345 | .append('svg:circle')
346 | .attr('r', config.polygonPointSize)
347 | .attr('cx', function (d, i) { return d.coordinates.x; })
348 | .attr('cy', function (d, i) { return d.coordinates.y; })
349 | .attr('fill', function (d, g) { return config.colors(g); })
350 | .on(over, _verticesTooltipShow)
351 | .on(out, _verticesTooltipHide);
352 | });
353 | };
354 |
355 | // builds out the polygon areas of the dataset
356 | const _buildPolygons = function (data) {
357 | chartVis.vertices
358 | .data(data).enter()
359 | .append('svg:polygon').classed('polygon-areas', true)
360 | .attr('points', function (group) { // build verticesString for each group
361 | let verticesString = '';
362 | group.axes.forEach(function (d) { verticesString += d.coordinates.x + ',' + d.coordinates.y + ' '; });
363 | return verticesString;
364 | })
365 | .attr('id', function (d, i) {return 'polygon_' + i;})
366 | .attr('stroke-width', '2px')
367 | .attr('stroke', function (d, i) { return config.colors(i); })
368 | .attr('fill', function (d, i) { return config.colors(i); })
369 | .attr('fill-opacity', config.polygonAreaOpacity)
370 | .attr('stroke-opacity', config.polygonStrokeOpacity)
371 | .on(over, function (d) {
372 | chartVis.svg.selectAll('.polygon-areas') // fade all other polygons out
373 | .transition(250)
374 | .attr('fill-opacity', 0.1)
375 | .attr('stroke-opacity', 0.1);
376 | d3.select(this) // focus on active polygon
377 | .transition(250)
378 | .attr('fill-opacity', 0.7)
379 | .attr('stroke-opacity', config.polygonStrokeOpacity);
380 | })
381 | .on(out, function () {
382 | d3.selectAll('.polygon-areas')
383 | .transition(250)
384 | .attr('fill-opacity', config.polygonAreaOpacity)
385 | .attr('stroke-opacity', 1);
386 | });
387 | };
388 |
389 |
390 | const _buildVis = function (data) {
391 | _buildVisComponents();
392 | _buildCoordinates(data);
393 | const showLevels = $scope.vis.params.addLevel;
394 | const showLevelsLabels = $scope.vis.params.addLevelLabel;
395 | const showAxes = $scope.vis.params.addAxe;
396 | const showAxesLabels = $scope.vis.params.addAxeLabel;
397 | const showLegend = $scope.vis.params.addLegend;
398 | const showVertices = $scope.vis.params.addVertice;
399 | const showPolygons = $scope.vis.params.addPolygon;
400 |
401 | if (showLevels) _buildLevels();
402 | if (showLevelsLabels) _buildLevelsLabels();
403 | if (showAxes) _buildAxes();
404 | if (showAxesLabels) _buildAxesLabels();
405 | if (showLegend) _buildLegend(data);
406 | if (showVertices) _buildVertices(data);
407 | if (showPolygons) _buildPolygons(data);
408 | };
409 |
410 |
411 | const _render = function () {
412 | d3.select(svgRoot).selectAll('svg').remove();
413 | if (config.facet) {
414 | data.forEach(function (d, i) {
415 | _buildVis([d]); // build svg for each data group
416 |
417 | // override colors
418 | chartVis.svg.selectAll('.polygon-areas')
419 | .attr('stroke', config.colors(i))
420 | .attr('fill', config.colors(i));
421 | chartVis.svg.selectAll('.polygon-vertices')
422 | .attr('fill', config.colors(i));
423 | chartVis.svg.selectAll('.legend-tiles')
424 | .attr('fill', config.colors(i));
425 | });
426 | } else {
427 | _buildVis(data); // build svg
428 | }
429 | };
430 |
431 |
432 | const off = $rootScope.$on('change:vis', function () {
433 | _updateDimensions();
434 | _initConfig();
435 | $scope.processTableGroups(tableGroups);
436 | _updateConfig();
437 | _render();
438 | });
439 |
440 | $scope.$on('$destroy', off);
441 |
442 | $scope.processTableGroups = function (tableGroups) {
443 | tableGroups.tables.forEach(function (table) {
444 | data = [];
445 | const cols = table.columns;
446 | table.rows.forEach(function (row,i) {
447 | const group = {};
448 | group.group = row[0];
449 | const axes = [];
450 | for (let i = 1; i < row.length; i++) {
451 | const item = {
452 | axis: cols[i].title,
453 | value: row[i]
454 | };
455 | axes.push(item);
456 | }
457 | group.axes = axes;
458 | data.push(group);
459 | });
460 | });
461 | };
462 |
463 | $scope.$watch('esResponse', function (resp) {
464 | if (resp) {
465 | tableGroups = tabifyAggResponse($scope.vis, resp);
466 | if (tableGroups.tables.length > 0) {
467 | if (tableGroups.tables[0].columns.length > 2) {
468 | _updateDimensions();
469 | _initConfig();
470 | $scope.processTableGroups(tableGroups);
471 | _updateConfig();
472 | _render();
473 | }
474 | }
475 | }
476 | });
477 | });
478 |
--------------------------------------------------------------------------------