From 56a958b624753918037844d7ed8945108843fcdd Mon Sep 17 00:00:00 2001
From: arch
Date: Thu, 20 Feb 2020 18:01:21 +0300
Subject: [PATCH] Initial commit. Parallel Coordinates just made its way in.
---
.eslintrc.js | 24 +
.gitignore | 4 +
.i18nrc.json | 9 +
README.md | 37 +
index.js | 22 +
package.json | 54 +
public/__tests__/index.js | 7 +
.../css/ParallelCoordinates.css | 134 +++
.../css/dataTables.alphabetSearch.css | 40 +
.../js/ParallelCoordinates.js | 930 ++++++++++++++++++
.../js/dataTables.alphabetSearch.js | 166 ++++
public/testviz/testviz.js | 49 +
public/testviz/testviz_components.js | 133 +++
public/testviz/testviz_editor.js | 24 +
translations/zh-CN.json | 84 ++
15 files changed, 1717 insertions(+)
create mode 100644 .eslintrc.js
create mode 100644 .gitignore
create mode 100644 .i18nrc.json
create mode 100644 README.md
create mode 100644 index.js
create mode 100644 package.json
create mode 100644 public/__tests__/index.js
create mode 100644 public/testviz/parallel_coordinates/css/ParallelCoordinates.css
create mode 100644 public/testviz/parallel_coordinates/css/dataTables.alphabetSearch.css
create mode 100644 public/testviz/parallel_coordinates/js/ParallelCoordinates.js
create mode 100644 public/testviz/parallel_coordinates/js/dataTables.alphabetSearch.js
create mode 100644 public/testviz/testviz.js
create mode 100644 public/testviz/testviz_components.js
create mode 100644 public/testviz/testviz_editor.js
create mode 100644 translations/zh-CN.json
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..9ca23b0
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,24 @@
+module.exports = {
+ root: true,
+ extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
+ settings: {
+ 'import/resolver': {
+ '@kbn/eslint-import-resolver-kibana': {
+ rootPackageName: 'test_3',
+ },
+ },
+ },
+ overrides: [
+ {
+ files: ['**/public/**/*'],
+ settings: {
+ 'import/resolver': {
+ '@kbn/eslint-import-resolver-kibana': {
+ forceNode: false,
+ rootPackageName: 'test_3',
+ },
+ },
+ },
+ },
+ ]
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..521e1c7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+npm-debug.log*
+node_modules
+/build/
+/public/app.css
diff --git a/.i18nrc.json b/.i18nrc.json
new file mode 100644
index 0000000..e7422b0
--- /dev/null
+++ b/.i18nrc.json
@@ -0,0 +1,9 @@
+{
+ "paths": {
+ "test3": "./"
+ },
+ "translations": [
+ "translations/zh-CN.json"
+ ]
+}
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d3fb939
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+# test-3
+
+> ASDasdkjflsd
+
+---
+
+## development
+
+See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. Once you have completed that, use the following yarn scripts.
+
+ - `yarn kbn bootstrap`
+
+ Install dependencies and crosslink Kibana and all projects/plugins.
+
+ > ***IMPORTANT:*** Use this script instead of `yarn` to install dependencies when switching branches, and re-run it whenever your dependencies change.
+
+ - `yarn start`
+
+ Start kibana and have it include this plugin. You can pass any arguments that you would normally send to `bin/kibana`
+
+ ```
+ yarn start --elasticsearch.hosts http://localhost:9220
+ ```
+
+ - `yarn build`
+
+ Build a distributable archive of your plugin.
+
+ - `yarn test:browser`
+
+ Run the browser tests in a real web browser.
+
+ - `yarn test:mocha`
+
+ Run the server tests using mocha.
+
+For more information about any of these commands run `yarn ${task} --help`. For a full list of tasks checkout the `package.json` file, or run `yarn run`.
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..dcdaa21
--- /dev/null
+++ b/index.js
@@ -0,0 +1,22 @@
+import { resolve } from 'path';
+import { existsSync } from 'fs';
+
+export default function(kibana) {
+ return new kibana.Plugin({
+ require: ['elasticsearch'],
+ name: 'test_3',
+ uiExports: {
+ /*app: {
+ title: 'Test 3',
+ description: 'ASDasdkjflsd',
+ main: 'plugins/test_3/app',
+ },
+ hacks: ['plugins/test_3/hack'],*/
+ styleSheetPaths: [
+ resolve(__dirname, 'public/parallel_coordinates/css/*'),
+ //resolve(__dirname, 'public/app.css'),
+ ].find(p => existsSync(p)),
+ visTypes: ['plugins/test_3/testviz/testviz'],
+ }
+ });
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3310927
--- /dev/null
+++ b/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "test_3",
+ "version": "0.0.3",
+ "description": "ASDasdkjflsd",
+ "main": "index.js",
+ "kibana": {
+ "version": "7.6.1",
+ "templateVersion": "1.0.0"
+ },
+ "scripts": {
+ "preinstall": "node ../../preinstall_check",
+ "kbn": "node ../../scripts/kbn",
+ "es": "node ../../scripts/es",
+ "lint": "eslint .",
+ "start": "plugin-helpers start",
+ "test:server": "plugin-helpers test:server",
+ "test:browser": "plugin-helpers test:browser",
+ "build": "plugin-helpers build"
+ },
+ "devDependencies": {
+ "@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana",
+ "@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana",
+ "@kbn/expect": "link:../../packages/kbn-expect",
+ "@kbn/i18n": "link:../../packages/kbn-i18n",
+ "@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers",
+ "babel-eslint": "^10.0.1",
+ "eslint": "^5.16.0",
+ "eslint-plugin-babel": "^5.3.0",
+ "eslint-plugin-import": "^2.16.0",
+ "eslint-plugin-jest": "^22.4.1",
+ "eslint-plugin-jsx-a11y": "^6.2.1",
+ "eslint-plugin-mocha": "^5.3.0",
+ "eslint-plugin-no-unsanitized": "^3.0.2",
+ "eslint-plugin-prefer-object-spread": "^1.2.1",
+ "eslint-plugin-react": "^7.12.4"
+ },
+ "dependencies": {
+ "d3": "3",
+ "datatables.mark.js": "^2.0.1",
+ "datatables.net-buttons-jqui": "^1.6.1",
+ "datatables.net-colreorder-jqui": "^1.5.2",
+ "datatables.net-fixedcolumns-jqui": "^3.3.0",
+ "datatables.net-fixedheader-jqui": "^3.1.6",
+ "datatables.net-jqui": "^1.10.20",
+ "datatables.net-responsive-jqui": "^2.2.3",
+ "jquery": "^3.4.1",
+ "jquery-ui": "^1.12.1",
+ "js-cookie": "^2.2.1",
+ "mark.js": "^8.11.1",
+ "react-html-id": "^0.1.5",
+ "select2": "^4.0.13",
+ "simple-statistics": "^7.0.8"
+ }
+}
diff --git a/public/__tests__/index.js b/public/__tests__/index.js
new file mode 100644
index 0000000..9320bd7
--- /dev/null
+++ b/public/__tests__/index.js
@@ -0,0 +1,7 @@
+import expect from '@kbn/expect';
+
+describe('suite', () => {
+ it('is a test', () => {
+ expect(true).to.equal(true);
+ });
+});
diff --git a/public/testviz/parallel_coordinates/css/ParallelCoordinates.css b/public/testviz/parallel_coordinates/css/ParallelCoordinates.css
new file mode 100644
index 0000000..e80f242
--- /dev/null
+++ b/public/testviz/parallel_coordinates/css/ParallelCoordinates.css
@@ -0,0 +1,134 @@
+@font-face {
+ font-family: "Oswald script=all rev=1";
+ src: url("https://fonts.gstatic.com/s/oswald/v29/TK3_WkUHHAIjg75cFRf3bXL8LICs1xZosUZiZQ.woff2") format("woff2");
+ font-style: normal;
+ font-weight: 700;
+ unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* Select part */
+.select2-container, .select2-choice, .select2-results__option {
+ font-size: 12px !important;
+}
+
+/* The SVG part */
+svg {
+ font: 10px sans-serif;
+}
+
+.background path {
+ fill: none;
+ stroke: #ddd5;
+ shape-rendering: crispEdges;
+}
+
+.foreground path {
+ fill: none;
+}
+
+.brush .extent {
+ fill-opacity: .3;
+ stroke: #fff;
+ shape-rendering: crispEdges;
+}
+
+.axis line,
+.axis path {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
+
+.axis text {
+ text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
+ cursor: move;
+}
+
+.bold {
+ stroke-width: 3;
+ stroke-opacity: 1.0;
+ filter: brightness(250%);
+}
+
+/* The tables part */
+mark {
+ background: orange;
+ color: black;
+}
+
+.table-selected-line {
+ background: #555 !important;
+ color: #fff;
+}
+
+.paginate_button{
+ font-size: 12px !important;
+}
+
+/* Clusters part */
+.ci-button-group::before ::after{
+ display: table;
+ content: ' ';
+ order: 1;
+ clear: both;
+}
+
+.ci-button-group{
+ display: flex;
+ flex-wrap: wrap;
+ max-height: 550px;
+}
+
+.ci-button {
+ padding: 1em 1em;
+ outline: 1px dashed lightgray;
+ text-shadow: #000000 1px 1px 0, 0px 0px 1px rgba(215, 142, 131, 0);
+ text-align: center;
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1.6;
+ color: #fefefe;
+ width: 45px;
+ height: 45px;
+
+ margin-left: 1px;
+ margin-top: 1px;
+}
+
+.ci-button:hover{
+ outline-width: 2px;
+ outline-color: gray;
+ z-index: 1;
+}
+
+.ci-button-group :last-child{
+ margin-bottom: 3px;
+}
+
+.ci-selected{
+ outline: 2px dashed black;
+ z-index: 1;
+}
+
+.ci-tooltip{
+ border-width: 1px;
+ border-radius: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ white-space: pre-line;
+}
+
+.ci-table{
+ width: 100%;
+ white-space: nowrap;
+ padding: 10px;
+}
+
+.ci-table h3,h5{
+ font-family: 'Oswald script=all rev=1' !important;
+ text-shadow: #ddd 1px 1px 0;
+}
+
+.ci-table h3{
+ font-size: 2.3rem;
+}
\ No newline at end of file
diff --git a/public/testviz/parallel_coordinates/css/dataTables.alphabetSearch.css b/public/testviz/parallel_coordinates/css/dataTables.alphabetSearch.css
new file mode 100644
index 0000000..087c921
--- /dev/null
+++ b/public/testviz/parallel_coordinates/css/dataTables.alphabetSearch.css
@@ -0,0 +1,40 @@
+div.alphabet {
+ position: relative;
+ display: table;
+ width: 100%;
+ margin-bottom: 1em;
+ white-space: nowrap;
+}
+
+div.alphabet span {
+ display: table-cell;
+ color: #3174c7;
+ cursor: pointer;
+ text-align: center;
+ width: 3.5%
+}
+
+div.alphabet span:hover {
+ text-decoration: underline;
+}
+
+div.alphabet span.active {
+ color: black;
+}
+
+div.alphabet span.empty {
+ color: red;
+}
+
+div.alphabetInfo {
+ display: block;
+ position: absolute;
+ background-color: #111;
+ border-radius: 3px;
+ color: white;
+ top: 2em;
+ height: 1.8em;
+ padding-top: 0.4em;
+ text-align: center;
+ z-index: 1;
+}
diff --git a/public/testviz/parallel_coordinates/js/ParallelCoordinates.js b/public/testviz/parallel_coordinates/js/ParallelCoordinates.js
new file mode 100644
index 0000000..f3d3d44
--- /dev/null
+++ b/public/testviz/parallel_coordinates/js/ParallelCoordinates.js
@@ -0,0 +1,930 @@
+// This function allows to jump to a certain row in a DataTable
+$.fn.dataTable.Api.register('row().show()', function () {
+ let page_info = this.table().page.info(),
+ // Get row index
+ new_row_index = this.index(),
+ // Row position
+ row_position = this.table().rows()[0].indexOf(new_row_index);
+ // Already on right page ?
+ if (row_position >= page_info.start && row_position < page_info.end) {
+ // Return row object
+ return this;
+ }
+ // Find page number
+ let page_to_display = Math.floor(row_position / this.table().page.len());
+ // Go to that page
+ this.table().page(page_to_display);
+ // Return row object
+ return this;
+});
+
+// This is used to manipulate d3 objects
+// e.g., to move a line on a graph to the front
+// https://github.com/wbkd/d3-extended
+d3.selection.prototype.moveToFront = function () {
+ return this.each(function () {
+ this.parentNode.appendChild(this);
+ });
+};
+d3.selection.prototype.moveToBack = function () {
+ return this.each(function () {
+ let firstChild = this.parentNode.firstChild;
+ if (firstChild) {
+ this.parentNode.insertBefore(this, firstChild);
+ }
+ });
+};
+
+// Add spaces and a dot to the number
+// '1234567.1234 -> 1 234 567.12'
+function numberWithSpaces(x) {
+ let parts = x.toString().split(".");
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
+ return parts.join(".");
+}
+
+// RGB color object to hex string
+function rgbToHex(color) {
+ return "#" + ((1 << 24) + (color.r * 255 << 16) + (color.g * 255 << 8)
+ + color.b * 255).toString(16).slice(1);
+}
+
+// Ability to count a number of a certain element in an array
+Object.defineProperties(Array.prototype, {
+ count: {
+ value: function(value) {
+ return this.filter(x => x==value).length;
+ }
+ }
+});
+
+class ParallelCoordinates {
+ // ********
+ // Constructor
+ //
+ // Passes all arguments to updateData(...)
+ // ********
+ constructor(element_id, dimension_names, data_array, clusters_list, clusters_color_scheme,
+ aux_features, aux_data_array, options = {}) {
+ // Save the time for debug purposes
+ this._timeStart = Date.now();
+
+ // Update data and draw the graph
+ if (arguments.length > 0)
+ {
+ this.updateData(element_id, dimension_names, data_array, clusters_list, clusters_color_scheme,
+ aux_features, aux_data_array, options);
+
+ if (this._debug)
+ console.log("Parallel Coordinates creation finished in %ims", Date.now() - this._timeStart);
+ }
+ }
+
+ // ********
+ // Data loading function
+ //
+ // Parameters:
+ // element_id - DOM id where to attach the Parallel Coordinates
+ // feature_names - array with feature names
+ // data_array - array with all data about objects under consideration
+ // clusters_list - array with all clusters in those data
+ // clusters_color_scheme - array with the color scheme
+ // aux_features - auxillary features that are not presented on the graph
+ // aux_data_array - auxillaty data
+ // options - graph options
+ //
+ // ********
+ updateData(element_id, feature_names, data_array, clusters_list, clusters_color_scheme,
+ aux_features, aux_data_array, options = {}) {
+ // Save the time for debug purposes
+ this._timeUpdate = Date.now();
+
+ // Store the new values
+ this.element_id = element_id;
+
+ // Update arrays
+ this._features = feature_names;
+ this._data = data_array;
+ this._color = clusters_list;
+ this._clusters_color_scheme = clusters_color_scheme;
+ this._aux_features = aux_features;
+ this._aux_data = aux_data_array;
+
+ // Debug statistics counters
+ this._search_quantity = 0;
+ this._search_time = 0;
+ this._search_time_min = -1;
+ this._search_time_max = -1;
+
+ // Arrays with (line id)<->(id in realData)
+ this._ids = this._data.map((row) => row[0]);
+
+ // If options does not have 'draw' option, make default one
+ if (!options.hasOwnProperty('draw') &&
+ (typeof this.options === 'undefined' ||
+ !this.options.hasOwnProperty('draw'))) {
+ options.draw = {
+ framework: "d3", // Possible values: 'd3'. todo: remove 'plotly' back
+ mode: "print" // Possible values: 'print', 'cluster'
+ //, cluster_tab_name: "Clusters" // Custom name for 'clusters' tab in the table
+ };
+
+ this.options = options;
+ }
+ else if (typeof this.options === 'undefined') this.options = options;
+ else if (options.hasOwnProperty('draw')) this.options.draw = options.draw;
+
+ // Throw an error if a wrong draw mode selected
+ if (!["print", "cluster"].includes(this.options.draw['mode']))
+ throw "Wrong mode value! Possible values: 'print', 'cluster', got: '"+ value + "'";
+
+ // If options does not have 'skip' option, make default one
+ // Default is to show 6 first lanes
+ if (!options.hasOwnProperty('skip') && !this.options.hasOwnProperty('skip'))
+ options.skip = {
+ dims: {
+ mode: "show", // Possible values: 'hide', 'show', 'none'
+ values: this._features.slice(0,
+ (this._features.length >= 5) ? 5 : this._features.length)
+ }
+ };
+ else if (options.hasOwnProperty('skip')) this.options.skip = options.skip;
+
+ // Check debug settings
+ if (options.hasOwnProperty('debug')) this._debug = options.debug;
+ else if (!this.hasOwnProperty('_debug')) this._debug = false;
+
+ // Initiate the arrays and draw the stuff
+ this._prepareGraphAndTables();
+
+ // Show update time when debug enabled
+ if (this._debug)
+ console.log("Parallel Coordinates updated in %ims (%ims from creation)",
+ Date.now() - this._timeUpdate, Date.now() - this._timeStart);
+ }
+
+ _prepareGraphAndTables() {
+ // A link to this ParCoord object
+ var _PCobject = this;
+ console.log(this);
+ // Clear the whole div if something is there
+ $("#" + this.element_id).empty();
+
+ // A selectBox with chosen features
+ d3.select("#" + this.element_id)
+ .append('p')
+ .text('Select the features displayed on the Parallel Coordinates graph:')
+
+ .append('select')
+ .attr({'class': 'select',
+ 'id': 's' + this.element_id});
+
+ // Construct the list with dimentions on graph
+ this._graph_features = this._features.filter((elem, i) => {
+ if (!('dims' in this.options.skip)) return true;
+ if (this.options.skip['dims'].mode === 'none') return true;
+ if (this.options.skip['dims'].mode === 'show' && this.options.skip['dims'].values.includes(elem)) return true;
+ return this.options.skip['dims'].mode === 'hide' && !this.options.skip['dims'].values.includes(elem);
+ });
+
+ // Options for selectBox
+ this._selectBox = $('#s' + this.element_id).select2({
+ closeOnSelect: false,
+ data: this._features.map((d, i) => { return { id: d, text: d, selected: this._graph_features.includes(d) }; }),
+ multiple: true,
+ width: 'auto'
+ })
+ // If the list changes - redraw the graph
+ .on("change.select2", () => {
+ this._graph_features = $('#s' + this.element_id).val();
+ this._createGraph();
+ });
+
+ this._selectBox.data('select2').$container.css("display", "block");
+
+ // Append an SVG to draw lines on
+ let container = d3.select("#" + this.element_id)
+ .append('div')
+ .attr('id', 'flex' + this.element_id)
+ .style({'display': 'flex',
+ 'width': 'auto',
+ 'overflow': 'auto',
+ 'flex-wrap': 'wrap'}),
+ svg_container = container.append("div")
+ .style({'width': 'auto',
+ 'flex': '0'});
+
+ this._graph = svg_container
+ .append("svg")
+ .style("overflow", "auto");
+
+ // A hint on how to use
+ //d3.select("#" + this.element_id)
+ svg_container
+ .append('p')
+ .html('Use the Left Mouse Button to select a curve and the corresponding line in the table
' +
+ 'Hover over the lines with mouse to see the row in the table');
+
+ // Currently selected line id
+ this._selected_line = -1;
+
+ // Add the table below the ParCoords
+ //d3.select("#" + this.element_id)
+ container
+ .append("div")
+ .attr("id", "t" + this.element_id + "_wrapper")
+ .style({"overflow": "auto",
+ "min-width": "500px",
+ "max-width": "max-content",
+ 'flex': '1'});
+
+ // Draw the graph and the table
+ this._createGraph();
+ this._createTable();
+
+ if(this.options.draw['mode'] === 'cluster'){
+ this._ci_div = d3.select("#" + this.element_id).append('div');
+ this._createClusterInfo();
+ }
+
+ // trash bin :)
+
+ /* $("#" + element_id + ".svg")
+ .tooltip({
+ track: true
+ });*/
+ // console.log('ids', _ids);
+
+ //console.log(_PCobject);
+ //bold[0][i].attr("display", "block");
+ //stroke: #0082C866;
+
+ /*_PCobject._datatable.rows().nodes()
+ .to$().removeClass('table-selected-line');*/
+
+ return this;
+ }
+
+ // Function to draw the graph
+ _createGraph() {
+ // A link to this ParCoord object
+ var _PCobject = this;
+
+ // Clear the graph div if something is there
+ if (this._svg !== undefined) this._svg.remove();
+
+ // Sizes of the graph
+ this._margin = { top: 30, right: 10, bottom: 10, left: 10 };
+ this._width = (this._graph_features.length > 7 ? 80 * this._graph_features.length : 600) - this._margin.left - this._margin.right;
+ this._height = 500 - this._margin.top - this._margin.bottom;
+
+ // Change the SVG size to draw lines on
+ this._graph.attr("width", this._width + this._margin.left + this._margin.right)
+ .attr("height", this._height + this._margin.top + this._margin.bottom);
+
+ // Arrays for x and y data, and brush dragging
+ this._x = d3.scale.ordinal().rangePoints([0, this._width], 1);
+ this._y = {};
+ this._dragging = {};
+ this._values = [];
+
+ // Line and axis parameters, arrays with lines (gray and colored)
+ this._line = d3.svg.line().interpolate("monotone");
+ this._axis = d3.svg.axis().orient("left");
+
+ // Shift the draw space
+ this._svg = this._graph.append("g")
+ .attr("transform", "translate(" + this._margin.left + "," + this._margin.top + ")");
+
+ // Extract the list of dimensions and create a scale for each
+ this._x.domain(this._graph_features.map((dim, index, arr) => {
+ this._values.push(this._data.map((row) => row[1][this._features.indexOf(dim)]));
+
+ this._y[dim] = d3.scale.linear()
+ .domain([Math.min(...this._values[index]), Math.max(...this._values[index])])
+ .range([this._height, 0]);
+
+ return dim;
+ }));
+
+ // Array to make brushes
+ this._line_data = [];
+ for (var i = 0; i < this._values[0].length; i++) {
+ let tmp = {};
+ for (var j = 0; j < this._values.length; j++) tmp[this._graph_features[j]] = this._values[j][i];
+ this._line_data.push(tmp);
+ }
+
+ // Grey background lines for context
+ this._background = this._svg.append("g")
+ .attr("class", "background")
+ .selectAll("path")
+ .data(this._line_data)
+ .enter().append("path")
+ .attr("d", this._path.bind(this));
+
+ // Foreground lines
+ this._foreground = this._svg.append("g")
+ .attr("class", "foreground")
+ .selectAll("path")
+ .data(this._line_data)
+ .enter().append("path")
+ .attr("d", this._path.bind(this))
+
+ // Cluster color scheme is applied to the stroke color
+ .attr("stroke", (d, i) => (
+ (this.options.draw['mode'] === "cluster")?
+ rgbToHex(this._clusters_color_scheme[this._color[i]]):
+ "#0082C866")
+ )
+ .attr("stroke-opacity", "0.4")
+
+ // When mouse is over the line, make it bold and colorful, move to the front
+ // and select a correspoding line in the table below
+ .on("mouseover", function (d, i) {
+ if (_PCobject._selected_line !== -1) return;
+
+ let time = Date.now();
+
+ $(this).addClass("bold");
+ d3.select(this).moveToFront();
+
+ let row = _PCobject._datatable.row((idx, data) => data[0] === _PCobject._parcoordsToTable(i));
+
+ row.show().draw(false);
+ _PCobject._datatable.rows(row).nodes().to$().addClass('table-selected-line');
+
+ // In case of debug enabled
+ // Write time to complete the search, average time, minimum and maximum
+ if (_PCobject._debug)
+ {
+ time = Date.now() - time;
+ _PCobject._search_time += time;
+ _PCobject._search_quantity += 1;
+
+ if (_PCobject._search_time_min === -1)
+ {
+ _PCobject._search_time_min = time;
+ _PCobject._search_time_max = time;
+ }
+
+ if (_PCobject._search_time_min > time) _PCobject._search_time_min = time;
+ else if (_PCobject._search_time_max < time) _PCobject._search_time_max = time;
+
+ console.log("Search completed for %ims, average: %sms [%i; %i].",
+ time, (_PCobject._search_time/_PCobject._search_quantity).toFixed(2),
+ _PCobject._search_time_min, _PCobject._search_time_max);
+ }
+ })
+
+ // When mouse is away, clear the effect
+ .on("mouseout", function (d, i) {
+ if (_PCobject._selected_line !== -1) return;
+
+ $(this).removeClass("bold");
+
+ let row = _PCobject._datatable.row((idx, data) => data[0] === _PCobject._parcoordsToTable(i));
+ _PCobject._datatable.rows(row).nodes().to$().removeClass('table-selected-line');
+ })
+
+ // Mouse click selects and deselects the line
+ .on("click", function (d, i) {
+ if (_PCobject._selected_line === -1) {
+ _PCobject._selected_line = i;
+
+ $(this).addClass("bold");
+ d3.select(this).moveToFront();
+
+ let row = _PCobject._datatable.row((idx, data) => data[0] === _PCobject._parcoordsToTable(i));
+
+ row.show().draw(false);
+ _PCobject._datatable.rows(row).nodes().to$().addClass('table-selected-line');
+ }
+ else if (_PCobject._selected_line === i) _PCobject._selected_line = -1;
+ });
+
+ // Add a group element for each dimension
+ this._g = this._svg.selectAll(".dimension")
+ .data(this._graph_features)
+ .enter().append("g")
+ .attr("class", "dimension")
+ .attr("transform", function (d) { return "translate(" + _PCobject._x(d) + ")"; })
+ .call(d3.behavior.drag()
+ .origin(function (d) { return { x: this._x(d) }; }.bind(this))
+ .on("dragstart", function (d) {
+ this._dragging[d] = this._x(d);
+ this._background.attr("visibility", "hidden");
+ }.bind(this))
+ .on("drag", function (d) {
+ this._dragging[d] = Math.min(this._width, Math.max(0, d3.event.x));
+ this._foreground.attr("d", this._path.bind(this));
+ this._graph_features.sort(function (a, b) { return this._position(a) - this._position(b); }.bind(this));
+ this._x.domain(this._graph_features);
+ this._g.attr("transform", function (d) { return "translate(" + this._position(d) + ")"; }.bind(this));
+ }.bind(this))
+ .on("dragend", function (d) {
+ delete _PCobject._dragging[d];
+ _PCobject._transition(d3.select(this)).attr("transform", "translate(" + _PCobject._x(d) + ")");
+ _PCobject._transition(_PCobject._foreground).attr("d", _PCobject._path.bind(_PCobject));
+ _PCobject._background
+ .attr("d", _PCobject._path.bind(_PCobject))
+ .transition()
+ .delay(500)
+ .duration(0)
+ .attr("visibility", null);
+ }));
+
+ // Add an axis and titles
+ this._g.append("g")
+ .attr("class", "axis")
+ .each(function (d) { d3.select(this).call(_PCobject._axis.scale(_PCobject._y[d])); })
+ .append("text")
+ .style("text-anchor", "middle")
+ .attr("y", -9)
+ .text(function (d) { return d; });
+
+ // Add and store a brush for each axis
+ this._g.append("g")
+ .attr("class", "brush")
+ .each(function (d) {
+ d3.select(this).call(
+ _PCobject._y[d].brush = d3.svg.brush()
+ .y(_PCobject._y[d])
+ .on("brushstart", _PCobject._brushstart)
+ .on("brush", _PCobject._brush.bind(this, _PCobject)));
+ })
+ .selectAll("rect")
+ .attr("x", -8)
+ .attr("width", 16);
+ }
+
+ // Creates a table below the ParallelCoordinates graph
+ _createTable() {
+ // A link to this ParCoord object
+ var _PCobject = this;
+
+ // Clear the table div if something is there
+ $('#t' + this.element_id + "_wrapper").empty();
+
+ // Add table to wrapper
+ d3.select("#t" + this.element_id + "_wrapper")
+ .append("table")
+ .attr({"id": "t" + this.element_id,
+ "class": "table hover"});
+
+ // 'visible' data array with lines on foreground (not filtered by a brush)
+ // possible values: ["all"] or ["id in _data", ...]
+ this._visible = ["all"];
+
+ // Initialize a search result with all objects visible
+ this._search_results = this._data.map((x) => x[0]);
+
+ // Array with headers
+ this._theader_array = this._features.slice();
+ if (this.options.draw['mode'] === "cluster")
+ if (this.options.draw.hasOwnProperty('cluster_tab_name'))
+ this._theader_array.unshift(this.options.draw['cluster_tab_name']);
+ else this._theader_array.unshift('Cluster');
+ this._theader_array.unshift('ID');
+ if (this._aux_features !== null) this._theader_array = this._theader_array.concat(this._aux_features);
+
+ // Map headers for the tables
+ this._theader = this._theader_array.map(row => {
+ return {
+ title: row,
+
+ // Add spaces and remove too much numbers after the comma
+ "render": function (data, type, full) {
+ if (type === 'display' && !isNaN(data))
+ return numberWithSpaces(parseFloat(Number(data).toFixed(2)));
+
+ return data;
+ }
+ };
+ });
+
+ // Array with table cell data
+ this._tcells = this._data.map((row, i) =>
+ [row[0]]
+ .concat((this.options.draw['mode'] === "cluster") ? [this._color[i]] : [])
+ .concat(row[1].map(String))
+ .concat((this._aux_data !== null)?this._aux_data[i][1].map(String):[])
+ .concat((this.options.draw['mode'] === "cluster") ?
+ [rgbToHex(this._clusters_color_scheme[this._color[i]])] : [])
+ );
+
+ // Vars for table and its datatable
+ this._table = $('#t' + this.element_id);
+ this._datatable = this._table.DataTable({
+ data: this._tcells,
+ columns: this._theader,
+
+ mark: true,
+ dom: 'Blfrtip',
+ colReorder: true,
+ buttons: ['colvis'],
+ "search": {"regex": true},
+
+ // Make colors lighter for readability
+ "rowCallback": (row, data) => {
+ if (this.options.draw['mode'] === "cluster")
+ $(row).children().css('background', data[data.length - 1] + "33");
+
+ $(row).children().css('white-space', 'nowrap');
+ },
+
+ // Redraw lines on ParCoords when table is ready
+ "fnDrawCallback": () => {
+ _PCobject._on_table_ready(_PCobject);
+ }
+ });
+
+ this._fix_css_in_table('t' + this.element_id);
+
+ // Add bold effect to lines when a line is hovered over in the table
+ $(this._datatable.table().body())
+ .on("mouseover", 'tr', function (d, i) {
+ if (_PCobject._selected_line !== -1) return;
+
+ let line = _PCobject._foreground[0][_PCobject._tableToParcoords(_PCobject._datatable.row(this).data()[0])];
+ $(line).addClass("bold");
+ d3.select(line).moveToFront();
+
+ $(_PCobject._datatable.rows().nodes()).removeClass('table-selected-line');
+ $(_PCobject._datatable.row(this).nodes()).addClass('table-selected-line');
+ })
+ .on("mouseout", 'tr', function (d) {
+ if (_PCobject._selected_line !== -1) return;
+
+ $(_PCobject._datatable.rows().nodes()).removeClass('table-selected-line');
+
+ $(_PCobject._foreground[0][
+ _PCobject._tableToParcoords(_PCobject._datatable.row(this).data()[0])
+ ]).removeClass("bold");
+ })
+
+ // If the line is clicked, make it 'selected'. Remove this status on one more click.
+ .on("click", 'tr', function (d, i) {
+ if (_PCobject._selected_line === -1) {
+ _PCobject._selected_line = _PCobject._tableToParcoords(_PCobject._datatable.row(this).data()[0]);
+
+ let line = _PCobject._foreground[0][_PCobject._selected_line];
+ $(line).addClass("bold");
+ d3.select(line).moveToFront();
+
+ _PCobject._datatable.rows(this).nodes().to$().addClass('table-selected-line');
+ }
+ else if (_PCobject._selected_line === _PCobject._tableToParcoords(_PCobject._datatable.row(this).data()[0])) {
+ let line = _PCobject._foreground[0][_PCobject._selected_line];
+ $(line).removeClass("bold");
+
+ _PCobject._selected_line = -1;
+ _PCobject._datatable.rows(this).nodes().to$().removeClass('table-selected-line');
+ }
+ });
+
+ // Add footer elements
+ this._table.append(
+ $('').append($('#t' + this.element_id + ' thead tr').clone())
+ );
+
+ // Add inputs to those elements
+ $('#t' + this.element_id + ' tfoot th').each(function (i, x) {
+ $(this).html('');
+ });
+
+ // Apply the search
+ this._datatable.columns().every(function (i, x) {
+ $('#t' + _PCobject.element_id + 'Input' + i).on('keyup change', function () {
+ _PCobject._datatable
+ .columns(i)
+ .search(this.value, true)
+ .draw();
+ });
+ });
+
+ // Callback for _search_results filling
+ $.fn.dataTable.ext.search.push(
+ function (settings, data, dataIndex, rowData, counter) {
+ if (settings.sTableId !== "t" + _PCobject.element_id) return true;
+
+ if (counter === 0) _PCobject._search_results = [];
+
+ if (_PCobject._visible[0] === "all" || _PCobject._visible.includes(data[0])) {
+ _PCobject._search_results.push(data[0]);
+
+ return true;
+ }
+ return false;
+ }
+ );
+ }
+
+ // Create cluster info buttons (which call the table creation)
+ _createClusterInfo() {
+ // Add a div to hold a label and buttons
+ this._ci_buttons_div = this._ci_div
+ .append('div')
+ .style({'margin-right': '15px',
+ 'flex-shrink': '0'});
+
+ // Add 'Choose Cluster' text to it
+ this._ci_buttons_div
+ .append('label')
+ .text("Choose Cluster");
+
+ // Add a div for the table
+ this._ci_table_div = this._ci_div.append('div');
+
+ //Add a div to hold the buttons after the label
+ this._ci_buttons = this._ci_buttons_div
+ .append('div')
+ .attr({'class': 'ci-button-group',
+ 'id': 'ci_buttons_' + this.element_id});
+
+ let cluster_count = d3.keys(this._clusters_color_scheme).map(x => this._color.count(x)),
+ scale = d3.scale.sqrt()
+ .domain([Math.min(...cluster_count), Math.max(...cluster_count)])
+ .range([11, 0]);
+
+ // Add corresponding buttons to every color
+ this._ci_buttons
+ .selectAll("a")
+ .data(d3.keys(this._clusters_color_scheme))
+ .enter().append('a')
+ .attr({'class': 'ci-button',
+ 'title': id => "Cluster " + id + ".\nElement count: " + cluster_count[id] + "."})
+ .style({'background': id => rgbToHex(this._clusters_color_scheme[id]),
+ 'box-shadow': id => 'inset 0px 0px 0px ' + scale(cluster_count[id]) + 'px #fff'})
+ .text(id => id)
+ .on("click", id => {
+ d3.event.preventDefault();
+
+ // Change the layout of the menu
+ // (only if the screen is wide enough)
+ if (!window.matchMedia("(max-width: 800px)").matches) {
+ // Make the elements side by side (buttons | table)
+ this._ci_div.style('display', 'flex');
+
+ // Make buttons vertical
+ this._ci_buttons
+ .style({'flex-direction': 'column',
+ 'align-items': 'center'});
+
+ // Calculate the Number Of Columns with buttons
+ let noc = Math.ceil(Object.keys(this._clusters_color_scheme).length / 11);
+
+ // Fix the div with the buttons a little bit
+ this._ci_buttons_div
+ .style('width', (noc * 46) + 'px')
+ .select('label')
+ .style('text-align', 'center');
+ }
+
+ // Clean all children
+ this._ci_table_div
+ .style('border', "5px dashed " + rgbToHex(this._clusters_color_scheme[id]) + "33")
+ .attr('class', 'ci-table')
+ .html('');
+
+ // Add the 'selected' decoration
+ this._ci_buttons_div.selectAll('*').classed('ci-selected', false);
+ d3.select(d3.event.target).classed('ci-selected', true);
+
+ // Add 'Cluster # statistics' text
+ this._ci_table_div
+ .append('h3')
+ .text("Cluster " + d3.event.target.innerText + " statistics");
+
+ // Print the stats
+ this._createClusterStatsTable();
+ });
+
+ $('.ci-button').tooltip({
+ track: true,
+ tooltipClass: "ci-tooltip",
+ show: false,
+ hide: false
+ });
+ }
+
+ // Creates a table with cluster info
+ // The function must be called from onClick, as it uses the d3.event.target
+ _createClusterStatsTable() {
+ // A link to this ParCoord object
+ var _PCobject = this;
+
+ // Make the header array
+ this._ci_header = ['', "Min", "Mean", "Max", "Median", "Deviation"].map((x, i) => { return {
+ title : x,
+ className: (i === 0)? 'firstCol':'',
+
+ // Add spaces and remove too much numbers after the comma
+ "render": function (data, type, full) {
+ if (type === 'display' && !isNaN(data))
+ return numberWithSpaces(parseFloat(Number(data).toFixed(2)));
+
+ return data;
+ }
+ }});
+
+ // Prepare cells
+ this._ci_cluster_data = this._data
+ .filter((x, i) => this._color[i] === d3.event.target.innerText)
+ .map((x, i) => x[1].concat(this._aux_data[i][1]));
+
+ this._ci_cells = this._features.concat(this._aux_features).map((x, i) =>
+ (this._features.includes(x)) ?
+ [
+ x,
+ d3.min(this._ci_cluster_data, row => row[i]),
+ d3.mean(this._ci_cluster_data, row => row[i]),
+ d3.max(this._ci_cluster_data, row => row[i]),
+ d3.median(this._ci_cluster_data, row => row[i]),
+ (this._ci_cluster_data.length > 1) ? d3.deviation(this._ci_cluster_data, row => row[i]) : '-'
+ ] : [x + ' (click to expand)', '-','-','-','-','-']);
+
+
+
+ /*
+ this._ci_stats = this._features.map((f, i) =>
+ {
+ let values = this._data
+ .filter((x, j) => this._color[j] === d3.event.target.innerText)
+ .map((data) => data[1][i]),
+ stat = [...new Set(values)].map((x)=>[x, values.count(x)]);
+
+ return [f, stat];
+ });
+ */
+
+ this._ci_aux_stats = this._aux_features.map((f, i) =>
+ {
+ let values = this._aux_data
+ .filter((x, j) => this._color[j] === d3.event.target.innerText)
+ .map((data) => data[1][i]),
+ stat = [...new Set(values)].map((x)=>[x, values.count(x)]);
+
+ return [f, stat];
+ });
+
+ // Add 'Number of elements: N' text
+ this._ci_table_div
+ .append('h5')
+ .text('Number of elements: ' + this._ci_cluster_data.length);
+
+ // Create the table
+ this._ci_table_div
+ .append('table')
+ .attr('id', 'ci_table_' + this.element_id);
+
+ // Add the data to the table
+ let table = $('#ci_table_' + this.element_id).DataTable({
+ data: this._ci_cells,
+ columns: this._ci_header,
+ mark: true,
+ dom: 'Alfrtip',
+ colReorder: true,
+ buttons: ['colvis'],
+ "search": {"regex": true}
+ });
+
+ // Add line getting darker on mouse hover
+ $(table.table().body())
+ .on("mouseover", 'tr', function (d, i) {
+ $(table.rows().nodes()).removeClass('table-selected-line');
+ $(table.row(this).nodes()).addClass('table-selected-line');
+ })
+ .on("mouseout", 'tr', function (d) {
+ $(table.rows().nodes()).removeClass('table-selected-line');
+ })
+ // Add event listener for opening and closing details
+ .on('click', 'td.firstCol', function(){
+ if (!this.innerText.endsWith(' (click to expand)')) return;
+
+ let id = _PCobject._aux_features.indexOf(this.innerText.replace(' (click to expand)', '')),
+ tr = $(this).closest('tr'),
+ row = table.row( tr ),
+ text = '
';
+
+ for(let i = 0; i<_PCobject._ci_aux_stats[id][1].length; i++)
+ text += '' +
+ _PCobject._ci_aux_stats[id][1][i][0] + ' | ' +
+ _PCobject._ci_aux_stats[id][1][i][1] +
+ ' |
';
+
+ text+='
';
+
+ if(row.child.isShown()){
+ // This row is already open - close it
+ row.child.hide();
+ tr.removeClass('shown');
+ } else {
+ // Open this row
+ row.child(text).show();
+ tr.addClass('shown');
+
+ let table = $('.ci_aux_table').DataTable({
+ columns:[
+ {title:_PCobject._aux_features[id]},
+ {title:"Count"}
+ ],
+ dom: 't',
+ order: [[1, "desc"]]
+ });
+
+ $(table.table().body())
+ .on("mouseover", 'tr', function (d, i) {
+ $(table.rows().nodes()).removeClass('table-selected-line');
+ $(table.row(this).nodes()).addClass('table-selected-line');
+ })
+ .on("mouseout", 'tr', function (d) {
+ $(table.rows().nodes()).removeClass('table-selected-line');
+ });
+ }
+ });
+
+ // Fix the css
+ this._fix_css_in_table('ci_table_' + this.element_id);
+ }
+
+ // Functions to perform id transformation
+ _tableToParcoords(index) { return this._ids.indexOf(index); }
+ _parcoordsToTable(index) { return this._ids[index]; }
+
+ // Callback to change the lines visibility after 'draw()' completed
+ _on_table_ready(object) {
+ object._foreground.style("display", function (d, j) {
+ let isVisible = object._visible[0] === "all" || object._visible.includes(object._parcoordsToTable(j));
+
+ return isVisible && object._search_results.includes(object._parcoordsToTable(j)) ? null : "none";
+ });
+ }
+
+ // Bug fixes related to css
+ _fix_css_in_table(id){
+ d3.select('#' + id)
+ .style({'display': 'block',
+ 'width': 'auto',
+ 'overflow': 'auto'});
+
+ d3.select('#' + id + '_paginate')
+ .style({'display': 'flex',
+ 'justify-content': 'flex-end',
+ 'float': 'right',
+ 'width': 'max-content'});
+
+ d3.select('#' + id + '_info')
+ .style({'display': 'inline-block',
+ 'width': 'max-content'});
+
+ document.getElementById(id + '_length').children[0].style = 'font-size: 12px !important;';
+ document.getElementById(id + '_length').children[0].children[0].style = 'height: auto;';
+ document.getElementById(id + '_filter').children[0].style = 'font-size: 12px !important;';
+ }
+
+ // Functions for lines and brushes
+ _position(d) {
+ let v = this._dragging[d];
+ return v == null ? this._x(d) : v;
+ }
+
+ _transition(g) {
+ return g.transition().duration(500);
+ }
+
+ _brushstart() {
+ d3.event.sourceEvent.stopPropagation();
+ }
+
+ // Returns the path for a given data point
+ _path(d) {
+ return this._line(
+ this._graph_features.map(
+ function (p) { return [this._position(p), this._y[p](d[p])]; },
+ this
+ )
+ );
+ }
+
+ // Handles a brush event, toggling the display of foreground lines
+ _brush(object) {
+ let actives = object._graph_features.filter(function (p) { return !object._y[p].brush.empty(); }),
+ extents = actives.map(function (p) { return object._y[p].brush.extent(); }),
+ visible = [];
+
+ if (actives.length === 0) visible.push("all");
+ else object._foreground.each(function (d, j) {
+ let isVisible = actives.every(function (p, i) {
+ return extents[i][0] <= d[p] && d[p] <= extents[i][1];
+ });
+
+ if (isVisible) visible.push(object._data[j][0]);
+ });
+
+ object._visible = visible;
+ object._datatable.draw();
+ }
+}
+
+export default ParallelCoordinates;
\ No newline at end of file
diff --git a/public/testviz/parallel_coordinates/js/dataTables.alphabetSearch.js b/public/testviz/parallel_coordinates/js/dataTables.alphabetSearch.js
new file mode 100644
index 0000000..f6f61d5
--- /dev/null
+++ b/public/testviz/parallel_coordinates/js/dataTables.alphabetSearch.js
@@ -0,0 +1,166 @@
+/*! AlphabetSearch for DataTables v1.0.0
+ * 2014 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary AlphabetSearch
+ * @description Show an alphabet aloneside a table providing search input options
+ * See http://datatables.net/blog/2014-09-22 for details
+ * @version 1.0.0
+ * @file dataTables.alphabetSearch.js
+ * @author SpryMedia Ltd (www.sprymedia.co.uk)
+ * @contact www.sprymedia.co.uk/contact
+ * @copyright Copyright 2014 SpryMedia Ltd.
+ *
+ * This source file is free software, available under the following license:
+ * MIT license - http://datatables.net/license/mit
+ *
+ * This source file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * For details please refer to: http://www.datatables.net
+ */
+(function(){
+
+
+// Search function
+$.fn.dataTable.Api.register( 'alphabetSearch()', function ( searchTerm ) {
+ this.iterator( 'table', function ( context ) {
+ context.alphabetSearch = searchTerm;
+ } );
+
+ return this;
+} );
+
+// Recalculate the alphabet display for updated data
+$.fn.dataTable.Api.register( 'alphabetSearch.recalc()', function ( searchTerm ) {
+ this.iterator( 'table', function ( context ) {
+ draw(
+ new $.fn.dataTable.Api( context ),
+ $('div.alphabet', this.table().container())
+ );
+ } );
+
+ return this;
+} );
+
+
+// Search plug-in
+$.fn.dataTable.ext.search.push( function ( context, searchData ) {
+ // Ensure that there is a search applied to this table before running it
+ if ( ! context.alphabetSearch ) {
+ return true;
+ }
+
+ if ( searchData[0].charAt(0).toUpperCase() === context.alphabetSearch ) {
+ return true;
+ }
+
+ return false;
+} );
+
+
+// Private support methods
+function bin ( data ) {
+ var letter, bins = {};
+
+ for ( var i=0, ien=data.length ; i')
+ .data( 'letter', '' )
+ .data( 'match-count', columnData.length )
+ .html( 'None' )
+ .appendTo( alphabet );
+
+ for ( var i=0 ; i<26 ; i++ ) {
+ var letter = String.fromCharCode( 65 + i );
+
+ $('')
+ .data( 'letter', letter )
+ .data( 'match-count', bins[letter] || 0 )
+ .addClass( ! bins[letter] ? 'empty' : '' )
+ .html( letter )
+ .appendTo( alphabet );
+ }
+
+ $('')
+ .appendTo( alphabet );
+}
+
+
+$.fn.dataTable.AlphabetSearch = function ( context ) {
+ var table = new $.fn.dataTable.Api( context );
+ var alphabet = $('');
+
+ draw( table, alphabet );
+
+ // Trigger a search
+ alphabet.on( 'click', 'span', function () {
+ alphabet.find( '.active' ).removeClass( 'active' );
+ $(this).addClass( 'active' );
+
+ table
+ .alphabetSearch( $(this).data('letter') )
+ .draw();
+ } );
+
+ // Mouse events to show helper information
+ alphabet
+ .on( 'mouseenter', 'span', function () {
+ alphabet
+ .find('div.alphabetInfo')
+ .css( {
+ opacity: 1,
+ left: $(this).position().left,
+ width: $(this).width()
+ } )
+ .html( $(this).data('match-count') );
+ } )
+ .on( 'mouseleave', 'span', function () {
+ alphabet
+ .find('div.alphabetInfo')
+ .css('opacity', 0);
+ } );
+
+ // API method to get the alphabet container node
+ this.node = function () {
+ return alphabet;
+ };
+};
+
+$.fn.DataTable.AlphabetSearch = $.fn.dataTable.AlphabetSearch;
+
+
+// Register a search plug-in
+$.fn.dataTable.ext.feature.push( {
+ fnInit: function ( settings ) {
+ var search = new $.fn.dataTable.AlphabetSearch( settings );
+ return search.node();
+ },
+ cFeature: 'A'
+} );
+
+
+}());
+
diff --git a/public/testviz/testviz.js b/public/testviz/testviz.js
new file mode 100644
index 0000000..c36687d
--- /dev/null
+++ b/public/testviz/testviz.js
@@ -0,0 +1,49 @@
+import { setup as visualizations } from '../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy';
+import { Schemas } from '../../../../src/legacy/ui/public/vis/editors/default/schemas';
+
+import { testvizEditor } from './testviz_editor';
+import { testvizComponent } from './testviz_components';
+
+visualizations.types.createReactVisualization({
+ name: 'testviz',
+ title: 'SefFFAF ASFASFASF',
+ icon: 'visControls',
+ description:
+ 'This visualizationASDASDERQ is able to SDKLFJHSDKLJFLKSDJ!!!!! e editor.',
+ //responseHandler: 'none',
+ visConfig: {
+ component: testvizComponent,
+ defaults: {
+ counter: 0,
+ },
+ },
+ editorConfig: {
+ optionTabs: [
+ {
+ name: 'options',
+ title: 'Options',
+ editor: testvizEditor,
+ },
+ ],
+ //hierarchicalData
+
+ schemas: new Schemas([
+ {
+ group: 'metrics',
+ name: 'metric',
+ title: 'Metric',
+ max: 10,
+ defaults: [
+ { type: 'max', schema: 'metric' }
+ ]
+ },{
+ group: 'buckets',
+ name: 'segment',
+ title: 'Bucket Split',
+ min: 0,
+ max: 10
+ }
+ ]),
+ },
+ //requestHandler: 'none',
+});
\ No newline at end of file
diff --git a/public/testviz/testviz_components.js b/public/testviz/testviz_components.js
new file mode 100644
index 0000000..4defab3
--- /dev/null
+++ b/public/testviz/testviz_components.js
@@ -0,0 +1,133 @@
+import React from 'react';
+
+import { EuiBadge } from '@elastic/eui';
+
+import "d3";
+import "jquery";
+import "jquery-ui";
+
+import "datatables.net";
+import "datatables.net-jqui";
+import "datatables.net-buttons-jqui";
+import "datatables.net-buttons/js/buttons.colVis.js";
+import "datatables.net-colreorder-jqui";
+import "datatables.net-fixedcolumns-jqui";
+import "datatables.net-fixedheader-jqui";
+import "datatables.net-responsive-jqui";
+
+import "select2";
+import "js-cookie";
+import "simple-statistics";
+import uniqueId from 'react-html-id';
+
+import ParallelCoordinates from './parallel_coordinates/js/ParallelCoordinates.js';
+
+import "select2/dist/css/select2.css";
+import "jquery-ui/themes/base/theme.css";
+import "datatables.net-jqui/css/dataTables.jqueryui.css";
+import "datatables.net-buttons-jqui/css/buttons.jqueryui.css";
+import "datatables.net-colreorder-jqui/css/colReorder.jqueryui.css";
+import "datatables.net-fixedcolumns-jqui/css/fixedColumns.jqueryui.css";
+import "datatables.net-fixedheader-jqui/css/fixedHeader.jqueryui.css";
+import "datatables.net-responsive-jqui/css/responsive.jqueryui.css";
+
+import './parallel_coordinates/css/ParallelCoordinates.css';
+
+///////
+//import "mark.js";
+//import "datatables.mark.js";
+//import('./parallel_coordinates/js/dataTables.alphabetSearch.js');
+//import './parallel_coordinates/css/dataTables.alphabetSearch.css';
+
+
+export class testvizComponent extends React.Component {
+ constructor() {
+ super();
+ uniqueId.enableUniqueIds(this);
+ }
+
+ onClick = () => {
+ this.props.vis.params.counter++;
+ this.props.vis.updateState();
+ };
+
+ render() {
+ console.log(this);
+
+ let visData = this.props.visData;
+ this._id = "ParallelCoordinatesGraph-" + this.nextUniqueId();
+
+ return (
+
+
+
+
+ {/*
+
+
+ {this.props.vis.params.counter}
+
+
+
AAAAAAAAAAAASDASDAS!
+
+
+
+ {visData.columns.map(col => {col.name} | )}
+
+
+ {visData.rows.map(row =>
+
+ {visData.columns.map(col => {row[col.id]} | )}
+
)
+ }
+
+
+
+ */}
+
+
+ );
+ }
+
+ updatePC(){
+ let dims = this.props.visData.columns.map(col => col.name).filter((x,i) => i > 0),
+ data = this.props.visData.rows.map( row =>
+ this.props.visData.columns.map(col => row[col.id])),
+ actual_data = data.map(row => {return [row[0], row.filter((x,i) => i > 0)]});
+
+ this._coords.updateData(this._id,
+ dims,
+ actual_data,
+ [],
+ [],
+ null,
+ null);
+ }
+
+ componentDidMount() {
+ let dims = this.props.visData.columns.map(col => col.name).filter((x,i) => i > 0),
+ data = this.props.visData.rows.map( row =>
+ this.props.visData.columns.map(col => row[col.id])),
+ actual_data = data.map(row => {return [row[0], row.filter((x,i) => i > 0)]});
+
+
+ this._coords = new ParallelCoordinates(this._id,
+ dims,
+ actual_data,
+ [],
+ [],
+ null,
+ null);
+
+ this.props.renderComplete();
+ }
+
+ componentDidUpdate() {
+ this.updatePC();
+ this.props.renderComplete();
+ }
+}
\ No newline at end of file
diff --git a/public/testviz/testviz_editor.js b/public/testviz/testviz_editor.js
new file mode 100644
index 0000000..c285d2c
--- /dev/null
+++ b/public/testviz/testviz_editor.js
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
+
+export class testvizEditor extends React.Component {
+ onCounterChange = ev => {
+ this.props.setValue('counter', parseInt(ev.target.value));
+ };
+
+ render() {
+
+
+ return (
+
+
+
+ );
+ }
+}
diff --git a/translations/zh-CN.json b/translations/zh-CN.json
new file mode 100644
index 0000000..46ce372
--- /dev/null
+++ b/translations/zh-CN.json
@@ -0,0 +1,84 @@
+{
+ "formats": {
+ "number": {
+ "currency": {
+ "style": "currency"
+ },
+ "percent": {
+ "style": "percent"
+ }
+ },
+ "date": {
+ "short": {
+ "month": "numeric",
+ "day": "numeric",
+ "year": "2-digit"
+ },
+ "medium": {
+ "month": "short",
+ "day": "numeric",
+ "year": "numeric"
+ },
+ "long": {
+ "month": "long",
+ "day": "numeric",
+ "year": "numeric"
+ },
+ "full": {
+ "weekday": "long",
+ "month": "long",
+ "day": "numeric",
+ "year": "numeric"
+ }
+ },
+ "time": {
+ "short": {
+ "hour": "numeric",
+ "minute": "numeric"
+ },
+ "medium": {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric"
+ },
+ "long": {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short"
+ },
+ "full": {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short"
+ }
+ },
+ "relative": {
+ "years": {
+ "units": "year"
+ },
+ "months": {
+ "units": "month"
+ },
+ "days": {
+ "units": "day"
+ },
+ "hours": {
+ "units": "hour"
+ },
+ "minutes": {
+ "units": "minute"
+ },
+ "seconds": {
+ "units": "second"
+ }
+ }
+ },
+ "messages": {
+ "test3.congratulationsText": "您已经成功创建第一个 Kibana 插件。",
+ "test3.congratulationsTitle": "恭喜!",
+ "test3.helloWorldText": "{title} 您好,世界!",
+ "test3.serverTimeText": "服务器时间(通过 API 调用)为 {time}"
+ }
+}