diff --git a/dashed/static/img/bubble.png b/dashed/assets/bubble.png similarity index 100% rename from dashed/static/img/bubble.png rename to dashed/assets/bubble.png diff --git a/dashed/static/img/cloud.png b/dashed/assets/cloud.png similarity index 100% rename from dashed/static/img/cloud.png rename to dashed/assets/cloud.png diff --git a/dashed/static/img/dash.png b/dashed/assets/dash.png similarity index 100% rename from dashed/static/img/dash.png rename to dashed/assets/dash.png diff --git a/dashed/static/img/dashed_screenshot.png b/dashed/assets/dashed_screenshot.png similarity index 100% rename from dashed/static/img/dashed_screenshot.png rename to dashed/assets/dashed_screenshot.png diff --git a/dashed/static/img/cardash.jpg b/dashed/assets/images/cardash.jpg similarity index 100% rename from dashed/static/img/cardash.jpg rename to dashed/assets/images/cardash.jpg diff --git a/dashed/static/img/gallery.jpg b/dashed/assets/images/gallery.jpg similarity index 100% rename from dashed/static/img/gallery.jpg rename to dashed/assets/images/gallery.jpg diff --git a/dashed/static/img/loading.gif b/dashed/assets/images/loading.gif similarity index 100% rename from dashed/static/img/loading.gif rename to dashed/assets/images/loading.gif diff --git a/dashed/static/img/serpe.jpg b/dashed/assets/images/serpe.jpg similarity index 100% rename from dashed/static/img/serpe.jpg rename to dashed/assets/images/serpe.jpg diff --git a/dashed/static/img/servers.jpg b/dashed/assets/images/servers.jpg similarity index 100% rename from dashed/static/img/servers.jpg rename to dashed/assets/images/servers.jpg diff --git a/dashed/static/img/slice.jpg b/dashed/assets/images/slice.jpg similarity index 100% rename from dashed/static/img/slice.jpg rename to dashed/assets/images/slice.jpg diff --git a/dashed/static/img/penguins.png b/dashed/assets/penguins.png similarity index 100% rename from dashed/static/img/penguins.png rename to dashed/assets/penguins.png diff --git a/dashed/assets/visualizations/table.js b/dashed/assets/visualizations/table.js index 937b0769bd19..d819fc3f5510 100644 --- a/dashed/assets/visualizations/table.js +++ b/dashed/assets/visualizations/table.js @@ -36,7 +36,8 @@ function tableVis(slice) { } var table = d3.select(slice.selector).append('table') - .classed('dataframe dataframe table table-striped table-bordered table-condensed table-hover dataTable no-footer', true); + .classed('dataframe dataframe table table-striped table-bordered table-condensed table-hover dataTable no-footer', true) + .attr('width', '100%'); table.append('thead').append('tr') .selectAll('th') diff --git a/dashed/static/favicon.png b/dashed/static/favicon.png deleted file mode 100644 index 50c8c9a45830..000000000000 Binary files a/dashed/static/favicon.png and /dev/null differ diff --git a/dashed/static/img/favicon.png b/dashed/static/img/favicon.png deleted file mode 100644 index 35fc3c161fd7..000000000000 Binary files a/dashed/static/img/favicon.png and /dev/null differ diff --git a/dashed/templates/dashed/base.html b/dashed/templates/dashed/base.html index da9e1cee91be..fa82fd0dd282 100644 --- a/dashed/templates/dashed/base.html +++ b/dashed/templates/dashed/base.html @@ -3,7 +3,7 @@ {% block head_css %} {{super()}} - + {% endblock %} {% block head_js %} diff --git a/dashed/templates/dashed/basic.html b/dashed/templates/dashed/basic.html index 8d0cba178ab4..5fea9e70b3d5 100644 --- a/dashed/templates/dashed/basic.html +++ b/dashed/templates/dashed/basic.html @@ -8,7 +8,7 @@ {% block head_meta %}{% endblock %} {% block head_css %} - + {% endblock %} {% block head_js %} diff --git a/docs/build.sh b/docs/build.sh index 20120b160b74..e4b62cd25dd5 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash rm -r _build make html -cp -r _build/html/ ../../dashed-docs/ +cp -r ../dashed/assets/images/ _build/html/_static/img/ diff --git a/panoramix/__init__.py b/panoramix/__init__.py deleted file mode 100644 index 7e2533ca4a2b..000000000000 --- a/panoramix/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Package's main module!""" - -import logging -import os -from flask import Flask, redirect -from flask.ext.appbuilder import SQLA, AppBuilder, IndexView -from flask.ext.appbuilder.baseviews import expose -from flask.ext.migrate import Migrate - - -APP_DIR = os.path.dirname(__file__) -CONFIG_MODULE = os.environ.get('PANORAMIX_CONFIG', 'panoramix.config') - -# Logging configuration -logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s') -logging.getLogger().setLevel(logging.DEBUG) - -app = Flask(__name__) -app.config.from_object(CONFIG_MODULE) -db = SQLA(app) -migrate = Migrate(app, db, directory=APP_DIR + "/migrations") - - -class MyIndexView(IndexView): - @expose('/') - def index(self): - return redirect('/panoramix/featured') - -appbuilder = AppBuilder( - app, db.session, - base_template='panoramix/base.html', - indexview=MyIndexView, - security_manager_class=app.config.get("CUSTOM_SECURITY_MANAGER")) - -sm = appbuilder.sm - -get_session = appbuilder.get_session -from panoramix import config, views # noqa diff --git a/panoramix/ascii_art.py b/panoramix/ascii_art.py deleted file mode 100644 index 4804469480c8..000000000000 --- a/panoramix/ascii_art.py +++ /dev/null @@ -1,68 +0,0 @@ -error = ( -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM8OI++=~~~~~~=+?IODMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMD$~~~~~~~~~~~~~~~~~~~~~~~=$MMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMN8?:~~~~~~~~~~~~~~~~~~~~~~~~~~=+8NMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMO=~~~~~~~~~~~~~~~~~+I??~~~~~~~~~~~~~+DMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMNI~~~~~~~~~~~~~~~~~~IIIII=~~~~~~~~~~~~~~=NMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMM+=~~~~~~~~~~~~~~~~~~~=III+~~~~~~~~~~~~~~~~~?8MMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+++=~~~~8MMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMI=~~~~~~~~~~~~~~~~~~~~~~~~~III?I~~~~~~~~,:++++++~~8MMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMN7~~~~~~~~~~~~~~~~==+=~~~~~~=IIIII~~~~~~:. ..:=++=~=MMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMO=~~~~~~~~~~~~~~~~+++=~~~~~~~~??I?I~~~~~~. ...,~~~~IMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMM~~~~~~~~~~~~~~~~~+++:,~~~~~~~~~~~?=~~~~~:. ..~~~~~OMMMMMMMMMMMM\n"+ -"MMMMMMMMM$=~~~~~~~~~~~~~~~=++:.. ..~~~~~~~~~~~~~~~~,. . . :~~~~~OMMMMMMMMMMM\n"+ -"MMMMMMMMM~~~~~~~~~~~~~~~~+++,. .~~~~~~~~~~~~~~~.. .. . .~~~~~=OMMMMMMMMMM\n"+ -"MMMMMMMM?~~~~~~~~~~~~~~~=+~. .~~~~~~~~~~~~~~. ,MMMMM,=~~~~~~NMMMMMMMMM\n"+ -"MMMMMMMN~~~~~~~~~~~~~~~~~,. .,~~~~~~~~~~~~~.. ZMMM,+Z:~~~~~~$MMMMMMMMM\n"+ -"MMMMMM8?~~~~~~~~~~~~~~~~~.. ..~~~~~~~~~~~~~:. DMMM,+D~~~~~~~~IMMMMMMMM\n"+ -"MMMMMMI~~~~~~~~~~~~~~~~~~.. :MMMO~~~~~~~~~~~~~~~,.. ?MMMMMI~~~~~~~~~MMMMMMMM\n"+ -"MMMMMM=~~~~~~~~~~~~~~~~~~.. MMM+=M:~~~~~~~~~~~~~:. .:IM$~~~~~~~~~~~8MMMMMMM\n"+ -"MMMMMD~~~~~~~~~~~~~~~~~~~:. MMM:,M:~~~~~~~~~~~~~~~.......:~~~~~~~~~~$MMMMMMM\n"+ -"MMMMMI~~~~~~~~~~~~~~~~~~~~, MMMMMM~~~~~~~~~~~~~~~~~~,..:~~~~~~~~~~~~+MMMMMMM\n"+ -"MMMMD+~~~~~~~~~~~~~~~~~~~~~. $MMMM$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=MMMMMMM\n"+ -"MMMM8~~~~~~~~~~~~~~~~~~~~~~:. . .:~~~~~~,..:. .=~~~~~~~~~~~~~~~~~~~~MMMMMMM\n"+ -"MMMMO~~~~~~~~~~~~~~~~~~~~~~~:, .:~~~~~=8.. .+ . =8ZI~~~~~~~~~~~~~~~~=MMMMMMM\n"+ -"MMMMZ=~~~~~~~~~~~~~~~~~~~~~~~~:,,,:~~~~~~IZ8:. .O....888?~~~~~~~~~~~~~~~+MMMMMMM\n"+ -"MMMMO=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?888=...I~I88888O?~~~~~~~~~~~~~~7MMMMMMM\n"+ -"MMMMO~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Z888OO88888888888O?~~~~~~~~~~~~~OMMMMMMM\n"+ -"MMMMD+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=8888888888888888888~~~~~~~~~~~~+MMMMMMMM\n"+ -"MMMMM7~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?8888888888888888888?~~~~~~~~~~=$MMMMMMMM\n"+ -"MMMMMD~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$8888888888888888888O~~~~~~~~~~8MMMMMMMMM\n"+ -"MMMMMN=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Z88888888888888888ZZ7=~~~~~~~~?MMMMMMMMMM\n"+ -"MMMMMMZ=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Z88888888Z7I===~~~~~~~~~~~~~=OMMMMMMMMMMM\n"+ -"MMMMMMN$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$88888O7?=~~~~~~~~~~~~~~~~~~OMMMMMMMMMMMM\n"+ -"MMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~I8OZ+~~~~~~~~~~~~~~~~~~~~=DMMMMMMMMMMMMMM\n"+ -"MMMMMMMM8=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+$+=~~~~~~~~~~~~~~~~~~~~+MMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMD7~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$DMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$OMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMD7=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ZMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMZ7=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~78MMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMM8OI=~~~~~~~~~~~~~~~~~~~=+?ZDNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMNDZ7?++~=~==~+?IONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+ -"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM") - -stacktrace=""" -------------------------------------------------------------------------------------------------------- -======================================================================================================= -------------------------------------------------------------------------------------------------------- - ___ ___ ___ - ( ) ( ) ( ) - .--. | |_ .---. .--. | | ___ | |_ ___ .-. .---. .--. .--. - / _ \ ( __) / .-, \ / \ | | ( ) ( __) ( ) \ / .-, \ / \ / \\ - . .' `. ; | | (__) ; | | .-. ; | | ' / | | | ' .-. ; (__) ; | | .-. ; | .-. ; - | ' | | | | ___ .'` | | |(___) | |,' / | | ___ | / (___) .'` | | |(___) | | | | - _\_`.(___) | |( ) / .'| | | | | . '. | |( ) | | / .'| | | | | |/ | -( ). '. | | | | | / | | | | ___ | | `. \ | | | | | | | / | | | | ___ | ' _.' - | | `\ | | ' | | ; | ; | | '( ) | | \ \ | ' | | | | ; | ; | | '( ) | .'.-. - ; '._,' ' ' `-' ; ' `-' | ' `-' | | | \ . ' `-' ; | | ' `-' | ' `-' | ' `-' / - '.___.' `.__. `.__.'_. `.__,' (___ ) (___) `.__. (___) `.__.'_. `.__,' `.__.' - -------------------------------------------------------------------------------------------------------- -======================================================================================================= -------------------------------------------------------------------------------------------------------- -""" diff --git a/panoramix/assets/.babelrc b/panoramix/assets/.babelrc deleted file mode 100644 index ad8d1fe608ec..000000000000 --- a/panoramix/assets/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets" : ["es2015", "react"] -} diff --git a/panoramix/assets/.eslintignore b/panoramix/assets/.eslintignore deleted file mode 100644 index 926b5a451b0a..000000000000 --- a/panoramix/assets/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/* -vendor/* -javascripts/dist/* diff --git a/panoramix/assets/.eslintrc b/panoramix/assets/.eslintrc deleted file mode 100644 index 882ad973a86e..000000000000 --- a/panoramix/assets/.eslintrc +++ /dev/null @@ -1,234 +0,0 @@ -{ - "root": true, - - "globals": { - "Symbol": false, - "Map": false, - "Set": false, - "Reflect": false, - }, - - "env": { - "es6": false, - "browser": true, - "node": true, - }, - - "parserOptions": { - "ecmaVersion": 5, - "sourceType": "module" - }, - - "rules": { - "array-bracket-spacing": [2, "never", { - "singleValue": false, - "objectsInArrays": false, - "arraysInArrays": false - }], - "array-callback-return": [2], - "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "callback-return": [2, ["callback"]], - "camelcase": [0], - "comma-dangle": [2, "never"], - "comma-spacing": [2], - "comma-style": [2, "last"], - "curly": [2, "all"], - "eqeqeq": 2, - "func-names": [0], - "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], - "key-spacing": [2, { "beforeColon": false, "afterColon": true }], - "keyword-spacing": [2, { - "before": true, - "after": true, - "overrides": { - "return": { "after": true }, - "throw": { "after": true }, - "case": { "after": true } - } - }], - "linebreak-style": [2, "unix"], - "lines-around-comment": [2, { - "beforeBlockComment": false, - "afterBlockComment": false, - "beforeLineComment": false, - "allowBlockStart": true, - "allowBlockEnd": true - }], - "max-depth": [2, 5], - "max-len": [0, 80, 4], - "max-nested-callbacks": [1, 2], - "max-params": [1, 4], - "new-parens": [2], - "newline-after-var": [0], - "no-bitwise": [0], - "no-cond-assign": [2], - "no-console": [2], - "no-const-assign": [2], - "no-constant-condition": [2], - "no-control-regex": [2], - "no-debugger": [2], - "no-delete-var": [2], - "no-dupe-args": [2], - "no-dupe-class-members": [2], - "no-dupe-keys": [2], - "no-duplicate-case": [2], - "no-else-return": [0], - "no-empty": [2], - "no-eq-null": [0], - "no-eval": [2], - "no-ex-assign": [2], - "no-extend-native": [2], - "no-extra-bind": [2], - "no-extra-boolean-cast": [2], - "no-extra-label": [2], - "no-extra-parens": [0], // needed for clearer #math eg (a - b) / c - "no-extra-semi": [2], - "no-fallthrough": [2], - "no-floating-decimal": [2], - "no-func-assign": [2], - "no-implied-eval": [2], - "no-implicit-coercion": [2, { - "boolean": false, - "number": true, - "string": true - }], - "no-implicit-globals": [2], - "no-inline-comments": [0], - "no-invalid-regexp": [2], - "no-irregular-whitespace": [2], - "no-iterator": [2], - "no-label-var": [2], - "no-labels": [2, { "allowLoop": false, "allowSwitch": false }], - "no-lone-blocks": [2], - "no-lonely-if": [2], - "no-loop-func": [2], - "no-magic-numbers": [0], // doesn't work well with vis cosmetic constant - "no-mixed-requires": [1, false], - "no-mixed-spaces-and-tabs": [2, false], - "no-multi-spaces": [2, { - "exceptions": { - "ImportDeclaration": true, - "Property": true, - "VariableDeclarator": true - } - }], - "no-multi-str": [2], - "no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1 }], - "no-native-reassign": [2], - "no-negated-condition": [2], - "no-negated-in-lhs": [2], - "no-nested-ternary": [0], - "no-new": [2], - "no-new-func": [2], - "no-new-object": [2], - "no-new-require": [0], - "no-new-symbol": [2], - "no-new-wrappers": [2], - "no-obj-calls": [2], - "no-octal": [2], - "no-octal-escape": [2], - "no-path-concat": [0], - "no-process-env": [0], - "no-process-exit": [2], - "no-proto": [2], - "no-redeclare": [2], - "no-regex-spaces": [2], - "no-restricted-modules": [0], - "no-restricted-imports": [0], - "no-restricted-syntax": [2, - "DebuggerStatement", - "LabeledStatement", - "WithStatement" - ], - "no-return-assign": [2, "always"], - "no-script-url": [2], - "no-self-assign": [2], - "no-self-compare": [0], - "no-sequences": [2], - "no-shadow-restricted-names": [2], - "no-spaced-func": [2], - "no-sparse-arrays": [2], - "no-sync": [0], - "no-ternary": [0], - "no-this-before-super": [2], - "no-throw-literal": [2], - "no-trailing-spaces": [2, { "skipBlankLines": false }], - "no-undef": [2, { "typeof": true }], - "no-undef-init": [2], - "no-undefined": [0], - "no-underscore-dangle": [0], // __data__ sometimes - "no-unexpected-multiline": [2], - "no-unmodified-loop-condition": [2], - "no-unneeded-ternary": [2], - "no-unreachable": [2], - "no-unused-expressions": [2], - "no-unused-labels": [2], - "no-unused-vars": [2, { - "vars": "all", - "args": "none", // (d, i) pattern d3 func makes difficult to enforce - "varsIgnorePattern": "jQuery" - }], - "no-use-before-define": [0], - "no-useless-call": [2], - "no-useless-concat": [2], - "no-useless-constructor": [2], - "no-void": [0], - "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], - "no-with": [2], - "no-whitespace-before-property": [2], - "object-curly-spacing": [2, "always"], - "object-shorthand": [2, "never"], - "one-var": [0], - "one-var-declaration-per-line": [2, "initializations"], - "operator-assignment": [0, "always"], - "padded-blocks": [0], - "prefer-arrow-callback": [0], - "prefer-const": [0], - "prefer-reflect": [0], - "prefer-rest-params": [0], - "prefer-spread": [0], - "prefer-template": [0], - "quote-props": [2, "as-needed", { "keywords": true }], - "radix": [2], - "require-yield": [2], - "semi": [2], - "semi-spacing": [2, { "before": false, "after": true }], - "sort-vars": [0], - "sort-imports": [0], - "space-before-function-paren": [2, { "anonymous": "always", "named": "never" }], - "space-before-blocks": [2, { "functions": "always", "keywords": "always" }], - "space-in-brackets": [0, "never", { - "singleValue": true, - "arraysInArrays": false, - "arraysInObjects": false, - "objectsInArrays": true, - "objectsInObjects": true, - "propertyName": false - }], - }, - // Temporarily not enforced - "new-cap": [2], // @TODO more tricky for the moment - "newline-per-chained-call": [2, { "ignoreChainWithDepth": 6 }], - "no-param-reassign": [0], // turn on once default args supported - "no-shadow": [2, { // @TODO more tricky for the moment with eg 'data' - "builtinGlobals": false, - "hoist": "functions", - "allow": ["i", "d"] - }], - "space-in-parens": [2, "never"], - "space-infix-ops": [2], - "space-unary-ops": [2, { "words": true, "nonwords": false }], - "spaced-comment": [2, "always", { "markers": ["!"] }], - "spaced-line-comment": [0, "always"], - "strict": [2, "global"], - "template-curly-spacing": [2, "never"], - "use-isnan": [2], - "valid-jsdoc": [0], - "valid-typeof": [2], - "vars-on-top": [0], - "wrap-iife": [2], - "wrap-regex": [2], - "yield-star-spacing": [2, { "before": false, "after": true }], - "yoda": [2, "never", { "exceptRange": true, "onlyEquality": false }] -} diff --git a/panoramix/assets/images/favicon.png b/panoramix/assets/images/favicon.png deleted file mode 100644 index 77fd477def79..000000000000 Binary files a/panoramix/assets/images/favicon.png and /dev/null differ diff --git a/panoramix/assets/javascripts/css-theme.js b/panoramix/assets/javascripts/css-theme.js deleted file mode 100644 index 93722b9141bf..000000000000 --- a/panoramix/assets/javascripts/css-theme.js +++ /dev/null @@ -1 +0,0 @@ -require('../stylesheets/less/index.less'); diff --git a/panoramix/assets/javascripts/dashboard.js b/panoramix/assets/javascripts/dashboard.js deleted file mode 100644 index 3707702a1fbd..000000000000 --- a/panoramix/assets/javascripts/dashboard.js +++ /dev/null @@ -1,229 +0,0 @@ -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -var px = require('./modules/panoramix.js'); -var d3 = require('d3'); -require('bootstrap'); - -var ace = require('brace'); -require('brace/mode/css'); -require('brace/theme/crimson_editor'); - -require('./panoramix-select2.js'); -require('../node_modules/gridster/dist/jquery.gridster.min.css'); -require('../node_modules/gridster/dist/jquery.gridster.min.js'); - -var Dashboard = function (dashboardData) { - var dashboard = $.extend(dashboardData, { - filters: {}, - init: function () { - this.initDashboardView(); - var sliceObjects = [], - dash = this; - dashboard.slices.forEach(function (data) { - var slice = px.Slice(data, dash); - $("#slice_" + data.slice_id).find('a.refresh').click(function () { - slice.render(); - }); - sliceObjects.push(slice); - slice.render(); - }); - this.slices = sliceObjects; - }, - setFilter: function (slice_id, col, vals) { - this.addFilter(slice_id, col, vals, false); - }, - addFilter: function (slice_id, col, vals, merge) { - if (merge === undefined) { - merge = true; - } - if (!(slice_id in this.filters)) { - this.filters[slice_id] = {}; - } - if (!(col in this.filters[slice_id]) || !merge) { - this.filters[slice_id][col] = vals; - } else { - this.filters[slice_id][col] = d3.merge([this.filters[slice_id][col], vals]); - } - this.refreshExcept(slice_id); - }, - readFilters: function () { - // Returns a list of human readable active filters - return JSON.stringify(this.filters, null, 4); - }, - refreshExcept: function (slice_id) { - var immune = this.metadata.filter_immune_slices; - if (immune) { - this.slices.forEach(function (slice) { - if (slice.data.slice_id !== slice_id && immune.indexOf(slice.data.slice_id) === -1) { - slice.render(); - } - }); - } - }, - clearFilters: function (slice_id) { - delete this.filters[slice_id]; - this.refreshExcept(slice_id); - }, - removeFilter: function (slice_id, col, vals) { - if (slice_id in this.filters) { - if (col in this.filters[slice_id]) { - var a = []; - this.filters[slice_id][col].forEach(function (v) { - if (vals.indexOf(v) < 0) { - a.push(v); - } - }); - this.filters[slice_id][col] = a; - } - } - this.refreshExcept(slice_id); - }, - getSlice: function (slice_id) { - this.slices.forEach(function (slice, i) { - if (slice.slice_id === slice_id) { - return slice; - } - }); - }, - initDashboardView: function () { - dashboard = this; - var gridster = $(".gridster ul").gridster({ - autogrow_cols: true, - widget_margins: [10, 10], - widget_base_dimensions: [100, 100], - draggable: { - handle: '.drag' - }, - resize: { - enabled: true, - stop: function (e, ui, element) { - var slice_data = $(element).data('slice'); - if (slice_data) { - dashboard.getSlice(slice_data.slice_id).resize(); - } - } - }, - serialize_params: function (_w, wgd) { - return { - slice_id: $(_w).attr('slice_id'), - col: wgd.col, - row: wgd.row, - size_x: wgd.size_x, - size_y: wgd.size_y - }; - } - }).data('gridster'); - $("div.gridster").css('visibility', 'visible'); - $("#savedash").click(function () { - var expanded_slices = {}; - $.each($(".slice_info"), function (i, d) { - var widget = $(this).parents('.widget'); - var slice_description = widget.find('.slice_description'); - if (slice_description.is(":visible")) { - expanded_slices[$(d).attr('slice_id')] = true; - } - }); - var data = { - positions: gridster.serialize(), - css: editor.getValue(), - expanded_slices: expanded_slices - }; - $.ajax({ - type: "POST", - url: '/panoramix/save_dash/' + dashboard.id + '/', - data: { - data: JSON.stringify(data) - }, - success: function () { - alert("Saved!"); - }, - error: function () { - alert("Error :("); - } - }); - }); - - var editor = ace.edit("dash_css"); - editor.$blockScrolling = Infinity; - - editor.setTheme("ace/theme/crimson_editor"); - editor.setOptions({ - minLines: 16, - maxLines: Infinity, - useWorker: false - }); - editor.getSession().setMode("ace/mode/css"); - - $(".select2").select2({ - dropdownAutoWidth: true - }); - $("#css_template").on("change", function () { - var css = $(this).find('option:selected').data('css'); - editor.setValue(css); - - $('#dash_css').val(css); - injectCss("dashboard-template", css); - - }); - $('#filters').click(function () { - alert(dashboard.readFilters()); - }); - $("a.remove-chart").click(function () { - var li = $(this).parents("li"); - gridster.remove_widget(li); - }); - - $("li.widget").click(function (e) { - var $this = $(this); - var $target = $(e.target); - - if ($target.hasClass("slice_info")) { - $this.find(".slice_description").slideToggle(0, function () { - $this.find('.refresh').click(); - }); - } else if ($target.hasClass("controls-toggle")) { - $this.find(".chart-controls").toggle(); - } - }); - - editor.on("change", function () { - var css = editor.getValue(); - $('#dash_css').val(css); - injectCss("dashboard-template", css); - }); - - var css = $('.dashboard').data('css'); - injectCss("dashboard-template", css); - - // Injects the passed css string into a style sheet with the specified className - // If a stylesheet doesn't exist with the passed className, one will be injected into - function injectCss(className, css) { - - var head = document.head || document.getElementsByTagName('head')[0]; - var style = document.querySelector('.' + className); - - if (!style) { - if (className.split(' ').length > 1) { - throw new Error("This method only supports selections with a single class name."); - } - style = document.createElement('style'); - style.className = className; - style.type = 'text/css'; - head.appendChild(style); - } - - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - style.innerHTML = css; - } - } - } - }); - dashboard.init(); - return dashboard; -}; - -$(document).ready(function () { - Dashboard($('.dashboard').data('dashboard')); -}); diff --git a/panoramix/assets/javascripts/explore.js b/panoramix/assets/javascripts/explore.js deleted file mode 100644 index 74025c0802aa..000000000000 --- a/panoramix/assets/javascripts/explore.js +++ /dev/null @@ -1,334 +0,0 @@ -// Javascript for the explorer page -// Init explorer view -> load vis dependencies -> read data (from dynamic html) -> render slice -// nb: to add a new vis, you must also add a Python fn in viz.py -// -// js -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -var px = require('./modules/panoramix.js'); - -require('jquery-ui'); -$.widget.bridge('uitooltip', $.ui.tooltip); // Shutting down jq-ui tooltips -require('bootstrap'); - -require('./panoramix-select2.js'); - -require('../node_modules/bootstrap-toggle/js/bootstrap-toggle.min.js'); - -// css -require('../vendor/pygments.css'); -require('../node_modules/bootstrap-toggle/css/bootstrap-toggle.min.css'); - -var slice; - -function prepForm() { - var i = 1; - // Assigning the right id to form elements in filters - $("#filters > div").each(function () { - $(this).attr("id", function () { - return "flt_" + i; - }); - $(this).find("#flt_col_0") - .attr("id", function () { - return "flt_col_" + i; - }) - .attr("name", function () { - return "flt_col_" + i; - }); - $(this).find("#flt_op_0") - .attr("id", function () { - return "flt_op_" + i; - }) - .attr("name", function () { - return "flt_op_" + i; - }); - $(this).find("#flt_eq_0") - .attr("id", function () { - return "flt_eq_" + i; - }) - .attr("name", function () { - return "flt_eq_" + i; - }); - i++; - }); -} - -function renderSlice() { - prepForm(); - slice.render(); -} - -function initExploreView() { - - function druidify() { - $('div.alert').remove(); - history.pushState({}, document.title, slice.querystring()); - renderSlice(); - } - - function get_collapsed_fieldsets() { - var collapsed_fieldsets = $("#collapsed_fieldsets").val(); - - if (collapsed_fieldsets !== undefined && collapsed_fieldsets !== "") { - collapsed_fieldsets = collapsed_fieldsets.split('||'); - } else { - collapsed_fieldsets = []; - } - return collapsed_fieldsets; - } - - function toggle_fieldset(legend, animation) { - var parent = legend.parent(); - var fieldset = parent.find(".legend_label").text(); - var collapsed_fieldsets = get_collapsed_fieldsets(); - var index; - - if (parent.hasClass("collapsed")) { - if (animation) { - parent.find(".fieldset_content").slideDown(); - } else { - parent.find(".fieldset_content").show(); - } - parent.removeClass("collapsed"); - parent.find("span.collapser").text("[-]"); - - // removing from array, js is overcomplicated - index = collapsed_fieldsets.indexOf(fieldset); - if (index !== -1) { - collapsed_fieldsets.splice(index, 1); - } - } else { // not collapsed - if (animation) { - parent.find(".fieldset_content").slideUp(); - } else { - parent.find(".fieldset_content").hide(); - } - - parent.addClass("collapsed"); - parent.find("span.collapser").text("[+]"); - index = collapsed_fieldsets.indexOf(fieldset); - if (index === -1 && fieldset !== "" && fieldset !== undefined) { - collapsed_fieldsets.push(fieldset); - } - } - - $("#collapsed_fieldsets").val(collapsed_fieldsets.join("||")); - } - - $('legend').click(function () { - toggle_fieldset($(this), true); - }); - - function copyURLToClipboard(url) { - var textArea = document.createElement("textarea"); - textArea.style.position = 'fixed'; - textArea.style.left = '-1000px'; - textArea.value = url; - - document.body.appendChild(textArea); - textArea.select(); - - try { - var successful = document.execCommand('copy'); - if (!successful) { - throw new Error("Not successful"); - } - } catch (err) { - window.alert("Sorry, your browser does not support copying. Use Ctrl / Cmd + C!"); - } - document.body.removeChild(textArea); - return successful; - } - - $('#shortner').click(function () { - $.ajax({ - type: "POST", - url: '/r/shortner/', - data: { - data: '/' + window.location.pathname + slice.querystring() - }, - success: function (data) { - var close = ''; - var copy = ''; - var spaces = '   '; - var popover = data + spaces + copy + spaces + close; - - var $shortner = $('#shortner') - .popover({ - content: popover, - placement: 'left', - html: true, - trigger: 'manual' - }) - .popover('show'); - - $('#copy_url').tooltip().click(function () { - var success = copyURLToClipboard(data); - if (success) { - $(this).attr("data-original-title", "Copied!").tooltip('fixTitle').tooltip('show'); - window.setTimeout(destroyPopover, 1200); - } - }); - $('#close_shortner').click(destroyPopover); - - function destroyPopover() { - $shortner.popover('destroy'); - } - }, - error: function () { - alert("Error :("); - } - }); - }); - $("#viz_type").change(function () { - $("#query").submit(); - }); - - var collapsed_fieldsets = get_collapsed_fieldsets(); - for (var i = 0; i < collapsed_fieldsets.length; i++) { - toggle_fieldset($('legend:contains("' + collapsed_fieldsets[i] + '")'), false); - } - - $(".select2").select2({ - dropdownAutoWidth: true - }); - $(".select2Sortable").select2({ - dropdownAutoWidth: true - }); - $(".select2Sortable").select2Sortable({ - bindOrder: 'sortableStop' - }); - $("form").show(); - $('[data-toggle="tooltip"]').tooltip({ - container: 'body' - }); - $(".ui-helper-hidden-accessible").remove(); // jQuery-ui 1.11+ creates a div for every tooltip - - function set_filters() { - for (var i = 1; i < 10; i++) { - var eq = px.getParam("flt_eq_" + i); - if (eq !== '') { - add_filter(i); - } - } - } - set_filters(); - - function add_filter(i) { - var cp = $("#flt0").clone(); - $(cp).appendTo("#filters"); - $(cp).show(); - if (i !== undefined) { - $(cp).find("#flt_eq_0").val(px.getParam("flt_eq_" + i)); - $(cp).find("#flt_op_0").val(px.getParam("flt_op_" + i)); - $(cp).find("#flt_col_0").val(px.getParam("flt_col_" + i)); - } - $(cp).find('select').select2(); - $(cp).find('.remove').click(function () { - $(this).parent().parent().remove(); - }); - } - - $(window).bind("popstate", function (event) { - // Browser back button - var returnLocation = history.location || document.location; - // Could do something more lightweight here, but we're not optimizing - // for the use of the back button anyways - returnLocation.reload(); - }); - - $("#plus").click(add_filter); - $("#btn_save").click(function () { - var slice_name = prompt("Name your slice!"); - if (slice_name !== "" && slice_name !== null) { - $("#slice_name").val(slice_name); - prepForm(); - $("#action").val("save"); - $("#query").submit(); - } - }); - $("#btn_overwrite").click(function () { - var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?"); - if (flag) { - $("#action").val("overwrite"); - prepForm(); - $("#query").submit(); - } - }); - - $(".druidify").click(druidify); - - function create_choices(term, data) { - var filtered = $(data).filter(function () { - return this.text.localeCompare(term) === 0; - }); - if (filtered.length === 0) { - return { - id: term, - text: term - }; - } - } - - function initSelectionToValue(element, callback) { - callback({ - id: element.val(), - text: element.val() - }); - } - - $(".select2_freeform").each(function () { - var parent = $(this).parent(); - var name = $(this).attr('name'); - var l = []; - var selected = ''; - for (var i = 0; i < this.options.length; i++) { - l.push({ - id: this.options[i].value, - text: this.options[i].text - }); - if (this.options[i].selected) { - selected = this.options[i].value; - } - } - parent.append( - '' - ); - $("input[name='" + name + "']").select2({ - createSearchChoice: create_choices, - initSelection: initSelectionToValue, - dropdownAutoWidth: true, - multiple: false, - data: l - }); - $(this).remove(); - }); -} - -$(document).ready(function () { - initExploreView(); - - // Dynamically register this visualization - var visType = window.viz_type.value; - px.registerViz(visType); - - var data = $('.slice').data('slice'); - slice = px.Slice(data); - - // - $('.slice').data('slice', slice); - - // call vis render method, which issues ajax - renderSlice(); - - // make checkbox inputs display as toggles - $(':checkbox') - .addClass('pull-right') - .attr("data-onstyle", "default") - .bootstrapToggle({ - size: 'mini' - }); - - $('div.toggle').addClass('pull-right'); - slice.bindResizeToWindowResize(); -}); diff --git a/panoramix/assets/javascripts/featured.js b/panoramix/assets/javascripts/featured.js deleted file mode 100644 index cd549dcc9de2..000000000000 --- a/panoramix/assets/javascripts/featured.js +++ /dev/null @@ -1,19 +0,0 @@ -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -var px = require('./modules/panoramix.js'); - -require('bootstrap'); -require('datatables'); -require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css'); - -$(document).ready(function () { - $('#dataset-table').DataTable({ - bPaginate: false, - order: [ - [1, "asc"] - ] - }); - $('#dataset-table_info').remove(); - //$('input[type=search]').addClass('form-control'); # TODO get search box to look nice - $('#dataset-table').show(); -}); diff --git a/panoramix/assets/javascripts/index.jsx b/panoramix/assets/javascripts/index.jsx deleted file mode 100644 index a56496ee9a0e..000000000000 --- a/panoramix/assets/javascripts/index.jsx +++ /dev/null @@ -1,18 +0,0 @@ -var $ = require('jquery'); -var jQuery = $; -import React from 'react'; -import { render } from 'react-dom'; -import { Jumbotron } from 'react-bootstrap'; - -class App extends React.Component { - render () { - return ( - -

Panoramix

-

Extensible visualization tool for exploring data from any database.

-
- ); - } -} - -render(, document.getElementById('app')); diff --git a/panoramix/assets/javascripts/modules/panoramix.js b/panoramix/assets/javascripts/modules/panoramix.js deleted file mode 100644 index fc28ec362b9e..000000000000 --- a/panoramix/assets/javascripts/modules/panoramix.js +++ /dev/null @@ -1,300 +0,0 @@ -var $ = require('jquery'); -var jQuery = $; -var d3 = require('d3'); - -require('../../stylesheets/panoramix.css'); - -// vis sources -var sourceMap = { - area: 'nvd3_vis.js', - bar: 'nvd3_vis.js', - bubble: 'nvd3_vis.js', - big_number: 'big_number.js', - compare: 'nvd3_vis.js', - dist_bar: 'nvd3_vis.js', - directed_force: 'directed_force.js', - filter_box: 'filter_box.js', - heatmap: 'heatmap.js', - iframe: 'iframe.js', - line: 'nvd3_vis.js', - markup: 'markup.js', - para: 'parallel_coordinates.js', - pie: 'nvd3_vis.js', - pivot_table: 'pivot_table.js', - sankey: 'sankey.js', - sunburst: 'sunburst.js', - table: 'table.js', - word_cloud: 'word_cloud.js', - world_map: 'world_map.js' -}; - -var color = function () { - // Color related utility functions go in this object - var bnbColors = [ - //rausch hackb kazan babu lima beach barol - '#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c', - '#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a', - '#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e' - ]; - var spectrums = { - blue_white_yellow: ['#00d1c1', 'white', '#ffb400'], - fire: ['white', 'yellow', 'red', 'black'], - white_black: ['white', 'black'], - black_white: ['black', 'white'] - }; - var colorBnb = function () { - // Color factory - var seen = {}; - return function (s) { - // next line is for dashed series that should have the same color - s = s.replace('---', ''); - if (seen[s] === undefined) { - seen[s] = Object.keys(seen).length; - } - return this.bnbColors[seen[s] % this.bnbColors.length]; - }; - }; - var colorScalerFactory = function (colors, data, accessor) { - // Returns a linear scaler our of an array of color - if (!Array.isArray(colors)) { - colors = spectrums[colors]; - } - - var ext = [0, 1]; - if (data !== undefined) { - ext = d3.extent(data, accessor); - } - - var points = []; - var chunkSize = (ext[1] - ext[0]) / colors.length; - $.each(colors, function (i, c) { - points.push(i * chunkSize); - }); - return d3.scale.linear().domain(points).range(colors); - }; - return { - bnbColors: bnbColors, - category21: colorBnb(), - colorScalerFactory: colorScalerFactory - }; -}; - -var px = (function () { - - var visualizations = {}; - var slice; - - function getParam(name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); - return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); - } - - function UTC(dttm) { - return new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds()); - } - var tickMultiFormat = d3.time.format.multi([ - [".%L", function (d) { - return d.getMilliseconds(); - }], // If there are millisections, show only them - [":%S", function (d) { - return d.getSeconds(); - }], // If there are seconds, show only them - ["%a %b %d, %I:%M %p", function (d) { - return d.getMinutes() !== 0; - }], // If there are non-zero minutes, show Date, Hour:Minute [AM/PM] - ["%a %b %d, %I %p", function (d) { - return d.getHours() !== 0; - }], // If there are hours that are multiples of 3, show date and AM/PM - ["%a %b %d, %Y", function (d) { - return d.getDate() !== 1; - }], // If not the first of the month, do "month day, year." - ["%B %Y", function (d) { - return d.getMonth() !== 0 && d.getDate() === 1; - }], // If the first of the month, do "month day, year." - ["%Y", function (d) { - return true; - }] // fall back on month, year - ]); - - function formatDate(dttm) { - var d = UTC(new Date(dttm)); - //d = new Date(d.getTime() - 1 * 60 * 60 * 1000); - return tickMultiFormat(d); - } - - function timeFormatFactory(d3timeFormat) { - var f = d3.time.format(d3timeFormat); - return function (dttm) { - var d = UTC(new Date(dttm)); - return f(d); - }; - } - - var Slice = function (data, dashboard) { - var timer; - var token = $('#' + data.token); - var container_id = data.token + '_con'; - var selector = '#' + container_id; - var container = $(selector); - var slice_id = data.slice_id; - var dttm = 0; - var stopwatch = function () { - dttm += 10; - var num = dttm / 1000; - $('#timer').text(num.toFixed(2) + " sec"); - }; - var qrystr = ''; - var always = function (data) { - //Private f, runs after done and error - clearInterval(timer); - $('#timer').removeClass('btn-warning'); - }; - slice = { - data: data, - container: container, - container_id: container_id, - selector: selector, - querystring: function () { - var parser = document.createElement('a'); - parser.href = data.json_endpoint; - if (dashboard !== undefined) { - var flts = encodeURIComponent(JSON.stringify(dashboard.filters)); - qrystr = parser.search + "&extra_filters=" + flts; - } else if ($('#query').length === 0) { - qrystr = parser.search; - } else { - qrystr = '?' + $('#query').serialize(); - } - return qrystr; - }, - jsonEndpoint: function () { - var parser = document.createElement('a'); - parser.href = data.json_endpoint; - var endpoint = parser.pathname + this.querystring() + "&json=true"; - return endpoint; - }, - done: function (data) { - clearInterval(timer); - token.find("img.loading").hide(); - container.show(); - if (data !== undefined) { - $("#query_container").html(data.query); - } - $('#timer').removeClass('btn-warning'); - $('#timer').addClass('btn-success'); - $('span.query').removeClass('disabled'); - $('#json').click(function () { - window.location = data.json_endpoint; - }); - $('#standalone').click(function () { - window.location = data.standalone_endpoint; - }); - $('#csv').click(function () { - window.location = data.csv_endpoint; - }); - $('.btn-group.results span').removeAttr('disabled'); - always(data); - }, - error: function (msg) { - token.find("img.loading").hide(); - var err = '
' + msg + '
'; - container.html(err); - container.show(); - $('span.query').removeClass('disabled'); - $('#timer').addClass('btn-danger'); - always(data); - }, - width: function () { - return token.width(); - }, - height: function () { - var others = 0; - var widget = container.parents('.widget'); - var slice_description = widget.find('.slice_description'); - if (slice_description.is(":visible")) { - others += widget.find('.slice_description').height() + 25; - } - others += widget.find('.chart-header').height(); - return widget.height() - others - 10; - }, - bindResizeToWindowResize: function () { - var resizeTimer; - $(window).on('resize', function (e) { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(function () { - slice.resize(); - }, 500); - }); - }, - render: function () { - $('.btn-group.results span').attr('disabled', 'disabled'); - token.find("img.loading").show(); - container.hide(); - container.html(''); - dttm = 0; - timer = setInterval(stopwatch, 10); - $('#timer').removeClass('btn-danger btn-success'); - $('#timer').addClass('btn-warning'); - this.viz.render(); - }, - resize: function () { - token.find("img.loading").show(); - container.hide(); - container.html(''); - this.viz.render(); - this.viz.resize(); - }, - addFilter: function (col, vals) { - if (dashboard !== undefined) { - dashboard.addFilter(slice_id, col, vals); - } - }, - setFilter: function (col, vals) { - if (dashboard !== undefined) { - dashboard.setFilter(slice_id, col, vals); - } - }, - clearFilter: function () { - if (dashboard !== undefined) { - delete dashboard.clearFilter(slice_id); - } - }, - removeFilter: function (col, vals) { - if (dashboard !== undefined) { - delete dashboard.removeFilter(slice_id, col, vals); - } - } - }; - var visType = data.form_data.viz_type; - px.registerViz(visType); - slice.viz = visualizations[data.form_data.viz_type](slice); - return slice; - }; - - function registerViz(name) { - var visSource = sourceMap[name]; - - if (visSource) { - var visFactory = require('../../visualizations/' + visSource); - if (typeof visFactory === 'function') { - visualizations[name] = visFactory; - } - } else { - throw new Error("require(" + name + ") failed."); - } - } - - // Export public functions - return { - registerViz: registerViz, - Slice: Slice, - formatDate: formatDate, - timeFormatFactory: timeFormatFactory, - color: color(), - getParam: getParam - }; -})(); - -module.exports = px; diff --git a/panoramix/assets/javascripts/modules/utils.js b/panoramix/assets/javascripts/modules/utils.js deleted file mode 100644 index e582ab65b05f..000000000000 --- a/panoramix/assets/javascripts/modules/utils.js +++ /dev/null @@ -1,55 +0,0 @@ -var d3 = require('d3'); - -/* - Utility function that takes a d3 svg:text selection and a max width, and splits the - text's text across multiple tspan lines such that any given line does not exceed max width - - If text does not span multiple lines AND adjustedY is passed, will set the text to the passed val - */ -function wrapSvgText(text, width, adjustedY) { - var lineHeight = 1; // ems - - text.each(function () { - var text = d3.select(this), - words = text.text().split(/\s+/), - word, - line = [], - lineNumber = 0, - x = text.attr("x"), - y = text.attr("y"), - dy = parseFloat(text.attr("dy")), - tspan = text.text(null) - .append("tspan") - .attr("x", x) - .attr("y", y) - .attr("dy", dy + "em"); - - var didWrap = false; - - for (var i = 0; i < words.length; i++) { - word = words[i]; - line.push(word); - tspan.text(line.join(" ")); - - if (tspan.node().getComputedTextLength() > width) { - line.pop(); // remove word that pushes over the limit - tspan.text(line.join(" ")); - line = [word]; - tspan = text.append("tspan") - .attr("x", x) - .attr("y", y) - .attr("dy", ++lineNumber * lineHeight + dy + "em") - .text(word); - - didWrap = true; - } - } - if (!didWrap && typeof adjustedY !== "undefined") { - tspan.attr("y", adjustedY); - } - }); -} - -module.exports = { - wrapSvgText: wrapSvgText -}; diff --git a/panoramix/assets/javascripts/panoramix-select2.js b/panoramix/assets/javascripts/panoramix-select2.js deleted file mode 100644 index 2b15435121ab..000000000000 --- a/panoramix/assets/javascripts/panoramix-select2.js +++ /dev/null @@ -1,5 +0,0 @@ -require('../node_modules/select2/select2.css'); -require('../node_modules/select2-bootstrap-css/select2-bootstrap.min.css'); -require('../node_modules/jquery-ui/themes/base/jquery-ui.css'); -require('select2'); -require('../vendor/select2.sortable.js'); diff --git a/panoramix/assets/javascripts/sql.js b/panoramix/assets/javascripts/sql.js deleted file mode 100644 index daa33740e976..000000000000 --- a/panoramix/assets/javascripts/sql.js +++ /dev/null @@ -1,97 +0,0 @@ -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -require('select2'); -require('datatables'); -require('bootstrap'); - -var ace = require('brace'); -require('brace/mode/sql'); -require('brace/theme/crimson_editor'); - -$(document).ready(function () { - function getParam(name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); - return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); - } - - function initSqlEditorView() { - var database_id = $('#database_id').val(); - var editor = ace.edit("sql"); - editor.$blockScrolling = Infinity; - editor.getSession().setUseWrapMode(true); - - $('#sql').hide(); - editor.setTheme("ace/theme/crimson_editor"); - editor.setOptions({ - minLines: 16, - maxLines: Infinity - }); - editor.getSession().setMode("ace/mode/sql"); - editor.focus(); - $("select").select2({ - dropdownAutoWidth: true - }); - - function showTableMetadata() { - $(".metadata").load( - '/panoramix/table/' + database_id + '/' + $("#dbtable").val() + '/'); - } - $("#dbtable").on("change", showTableMetadata); - showTableMetadata(); - $("#create_view").click(function () { - alert("Not implemented"); - }); - $(".sqlcontent").show(); - - function selectStarOnClick() { - $.ajax('/panoramix/select_star/' + database_id + '/' + $("#dbtable").val() + '/') - .done(function (msg) { - editor.setValue(msg); - }); - } - - $("#select_star").click(selectStarOnClick); - - editor.setValue(getParam('sql')); - $(window).bind("popstate", function (event) { - // Could do something more lightweight here, but we're not optimizing - // for the use of the back button anyways - editor.setValue(getParam('sql')); - $("#run").click(); - }); - $("#run").click(function () { - $('#results').hide(0); - $('#loading').show(0); - history.pushState({}, document.title, '?sql=' + encodeURIComponent(editor.getValue())); - $.ajax({ - type: "POST", - url: '/panoramix/runsql/', - data: { - data: JSON.stringify({ - database_id: $('#database_id').val(), - sql: editor.getSession().getValue() - }) - }, - success: function (data) { - $('#loading').hide(0); - $('#results').show(0); - $('#results').html(data); - - $('table.sql_results').DataTable({ - paging: false, - searching: true, - aaSorting: [] - }); - }, - error: function (err, err2) { - $('#loading').hide(0); - $('#results').show(0); - $('#results').html(err.responseText); - } - }); - }); - } - initSqlEditorView(); -}); diff --git a/panoramix/assets/javascripts/standalone.js b/panoramix/assets/javascripts/standalone.js deleted file mode 100644 index 062b3446a446..000000000000 --- a/panoramix/assets/javascripts/standalone.js +++ /dev/null @@ -1,13 +0,0 @@ -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -var px = require('./modules/panoramix.js'); - -require('bootstrap'); - -$(document).ready(function () { - var slice; - var data = $('.slice').data('slice'); - slice = px.Slice(data); - slice.render(); - slice.bindResizeToWindowResize(); -}); diff --git a/panoramix/assets/package.json b/panoramix/assets/package.json deleted file mode 100644 index 650140db49cc..000000000000 --- a/panoramix/assets/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "panoramix", - "version": "0.1.0", - "description": "Any database to any visualization", - "directories": { - "doc": "docs", - "test": "tests" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "webpack -d --watch --colors", - "prod": "webpack -p --colors", - "lint": "npm run --silent lint:js", - "lint:js": "eslint --ignore-path=.eslintignore --ext .js .; exit 0;" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/mistercrunch/panoramix.git" - }, - "keywords": [ - "big", - "data", - "exploratory", - "analysis", - "react", - "d3", - "airbnb", - "nerds", - "database", - "flask" - ], - "author": "Airbnb", - "bugs": { - "url": "https://github.com/mistercrunch/panoramix/issues" - }, - "homepage": "https://github.com/mistercrunch/panoramix#readme", - "dependencies": { - "babel-loader": "^6.2.1", - "babel-polyfill": "^6.3.14", - "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", - "bootstrap": "^3.3.6", - "bootstrap-datepicker": "^1.6.0", - "bootstrap-toggle": "^2.2.1", - "brace": "^0.7.0", - "css-loader": "^0.23.1", - "d3": "^3.5.14", - "d3-cloud": "^1.2.1", - "d3-sankey": "^0.2.1", - "d3-tip": "^0.6.7", - "d3.layout.cloud": "^1.2.0", - "datamaps": "^0.4.4", - "datatables": "^1.10.9", - "datatables-bootstrap3-plugin": "^0.4.0", - "exports-loader": "^0.6.3", - "font-awesome": "^4.5.0", - "gridster": "^0.5.6", - "imports-loader": "^0.6.5", - "jquery": "^2.2.1", - "jquery-ui": "^1.10.5", - "less-loader": "^2.2.2", - "nvd3": "1.8.2", - "react": "^0.14.7", - "react-bootstrap": "^0.28.3", - "react-dom": "^0.14.7", - "select2": "3.5", - "select2-bootstrap-css": "^1.4.6", - "style-loader": "^0.13.0", - "topojson": "^1.6.22", - "webpack": "^1.12.12" - }, - "devDependencies": { - "eslint": "^2.2.0", - "file-loader": "^0.8.5", - "url-loader": "^0.5.7" - } -} diff --git a/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf b/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index 1413fc609ab6..000000000000 Binary files a/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.woff b/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 9e612858f802..000000000000 Binary files a/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.woff2 b/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.woff2 deleted file mode 100644 index 64539b54c375..000000000000 Binary files a/panoramix/assets/stylesheets/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ diff --git a/panoramix/assets/stylesheets/less/bootswatch.less b/panoramix/assets/stylesheets/less/bootswatch.less deleted file mode 100644 index e5edac2eb712..000000000000 --- a/panoramix/assets/stylesheets/less/bootswatch.less +++ /dev/null @@ -1,616 +0,0 @@ -// Paper 3.3.5 -// Bootswatch -// ----------------------------------------------------- - -@web-font-path: "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700"; - -.web-font(@path) { - @import url("@{path}"); -} -.web-font(@web-font-path); - -// Navbar ===================================================================== - -.navbar { - border: none; - .box-shadow(0 1px 2px rgba(0,0,0,.3)); - - &-brand { - font-size: 24px; - } - - &-inverse { - .navbar-form { - - input[type=text], - input[type=password] { - color: #fff; - .box-shadow(inset 0 -1px 0 @navbar-inverse-link-color); - .placeholder(@navbar-inverse-link-color); - - &:focus { - .box-shadow(inset 0 -2px 0 #fff); - } - } - } - } -} - -// Buttons ==================================================================== - -#btn(@class,@bg) { - .btn-@{class} { - background-size: 200%; - background-position: 50%; - - &:focus { - background-color: @bg; - } - - &:hover, - &:active:hover { - background-color: darken(@bg, 6%); - } - - &:active { - background-color: darken(@bg, 12%); - #gradient > .radial(darken(@bg, 12%) 10%, @bg 11%); - background-size: 1000%; - .box-shadow(2px 2px 4px rgba(0,0,0,.4)); - } - } -} - -#btn(default,@btn-default-bg); -#btn(primary,@btn-primary-bg); -#btn(success,@btn-success-bg); -#btn(info,@btn-info-bg); -#btn(warning,@btn-warning-bg); -#btn(danger,@btn-danger-bg); -#btn(link,#fff); - -.btn { - text-transform: uppercase; - border: none; - .box-shadow(1px 1px 4px rgba(0,0,0,.4)); - .transition(all 0.4s); - - &-link { - border-radius: @btn-border-radius-base; - .box-shadow(none); - color: @btn-default-color; - - &:hover, - &:focus { - .box-shadow(none); - color: @btn-default-color; - text-decoration: none; - } - } - - &-default { - - &.disabled { - background-color: rgba(0, 0, 0, 0.1); - color: rgba(0, 0, 0, 0.4); - opacity: 1; - } - } -} - -.btn-group { - .btn + .btn, - .btn + .btn-group, - .btn-group + .btn, - .btn-group + .btn-group { - margin-left: 0; - } - - &-vertical { - > .btn + .btn, - > .btn + .btn-group, - > .btn-group + .btn, - > .btn-group + .btn-group { - margin-top: 0; - } - } -} - -// Typography ================================================================= - -body { - -webkit-font-smoothing: antialiased; - letter-spacing: .1px; -} - -p { - margin: 0 0 1em; -} - -input, -button { - -webkit-font-smoothing: antialiased; - letter-spacing: .1px; -} - -a { - .transition(all 0.2s); -} - -// Tables ===================================================================== - -.table-hover { - > tbody > tr, - > tbody > tr > th, - > tbody > tr > td { - .transition(all 0.2s); - } -} - -// Forms ====================================================================== - -label { - font-weight: normal; -} - -textarea, -textarea.form-control, -input.form-control, -input[type=text], -input[type=password], -input[type=email], -input[type=number], -[type=text].form-control, -[type=password].form-control, -[type=email].form-control, -[type=tel].form-control, -[contenteditable].form-control { - padding: 0; - border: none; - border-radius: 0; - -webkit-appearance: none; - .box-shadow(inset 0 -1px 0 #ddd); - font-size: 16px; - - &:focus { - .box-shadow(inset 0 -2px 0 @brand-primary); - } - - &[disabled], - &[readonly] { - .box-shadow(none); - border-bottom: 1px dotted #ddd; - } - - &.input { - &-sm { - font-size: @font-size-small; - } - - &-lg { - font-size: @font-size-large; - } - } -} - -select, -select.form-control { - border: 0; - border-radius: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - padding-left: 0; - padding-right: 0\9; // remove padding for < ie9 since default arrow can't be removed - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEVmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmaP/QSjAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=); - background-size: 13px; - background-repeat: no-repeat; - background-position: right center; - .box-shadow(inset 0 -1px 0 #ddd); - font-size: 16px; - line-height: 1.5; - - &::-ms-expand { - display: none; - } - - &.input { - &-sm { - font-size: @font-size-small; - } - - &-lg { - font-size: @font-size-large; - } - } - - &:focus { - .box-shadow(inset 0 -2px 0 @brand-primary); - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEUhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISF8S9ewAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=); - } - - &[multiple] { - background: none; - } -} - -.radio, -.radio-inline, -.checkbox, -.checkbox-inline { - label { - padding-left: 25px; - } - - input[type="radio"], - input[type="checkbox"] { - margin-left: -25px; - } -} - -input[type="radio"], -.radio input[type="radio"], -.radio-inline input[type="radio"] { - position: relative; - margin-top: 6px; - margin-right: 4px; - vertical-align: top; - border: none; - background-color: transparent; - -webkit-appearance: none; - appearance: none; - cursor: pointer; - - &:focus { - outline: none; - } - - &:before, - &:after { - content: ""; - display: block; - width: 18px; - height: 18px; - border-radius: 50%; - .transition(240ms); - } - - &:before { - position: absolute; - left: 0; - top: -3px; - background-color: @brand-primary; - .scale(0); - } - - &:after { - position: relative; - top: -3px; - border: 2px solid @gray; - } - - &:checked:before { - .scale(0.5); - } - - &:disabled:checked:before { - background-color: @gray-light; - } - - &:checked:after { - border-color: @brand-primary; - } - - &:disabled:after, - &:disabled:checked:after { - border-color: @gray-light; - } -} - -input[type="checkbox"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: relative; - border: none; - margin-bottom: -4px; - -webkit-appearance: none; - appearance: none; - cursor: pointer; - - &:focus { - outline: none; - } - - &:focus:after { - border-color: @brand-primary; - } - - &:after { - content: ""; - display: block; - width: 18px; - height: 18px; - margin-top: -2px; - margin-right: 5px; - border: 2px solid @gray; - border-radius: 2px; - .transition(240ms); - } - - &:checked:before { - content: ""; - position: absolute; - top: 0; - left: 6px; - display: table; - width: 6px; - height: 12px; - border: 2px solid #fff; - border-top-width: 0; - border-left-width: 0; - .rotate(45deg); - } - - &:checked:after { - background-color: @brand-primary; - border-color: @brand-primary; - } - - &:disabled:after { - border-color: @gray-light; - } - - &:disabled:checked:after { - background-color: @gray-light; - border-color: transparent; - } -} - -.has-warning { - input:not([type=checkbox]), - .form-control, - input.form-control[readonly], - input[type=text][readonly], - [type=text].form-control[readonly], - input:not([type=checkbox]):focus, - .form-control:focus { - border-bottom: none; - .box-shadow(inset 0 -2px 0 @brand-warning); - } -} - -.has-error { - input:not([type=checkbox]), - .form-control, - input.form-control[readonly], - input[type=text][readonly], - [type=text].form-control[readonly], - input:not([type=checkbox]):focus, - .form-control:focus { - border-bottom: none; - .box-shadow(inset 0 -2px 0 @brand-danger); - } -} - -.has-success { - input:not([type=checkbox]), - .form-control, - input.form-control[readonly], - input[type=text][readonly], - [type=text].form-control[readonly], - input:not([type=checkbox]):focus, - .form-control:focus { - border-bottom: none; - .box-shadow(inset 0 -2px 0 @brand-success); - } -} - -// Remove the Bootstrap feedback styles for input addons -.input-group-addon { - .has-warning &, .has-error &, .has-success & { - color: @input-color; - border-color: @input-group-addon-border-color; - background-color: @input-group-addon-bg; - } -} - -// Navs ======================================================================= - -.nav-tabs { - > li > a, - > li > a:focus { - margin-right: 0; - background-color: transparent; - border: none; - color: @navbar-default-link-color; - .box-shadow(inset 0 -1px 0 #ddd); - .transition(all 0.2s); - - &:hover { - background-color: transparent; - .box-shadow(inset 0 -2px 0 @brand-primary); - color: @brand-primary; - } - } - - & > li.active > a, - & > li.active > a:focus { - border: none; - .box-shadow(inset 0 -2px 0 @brand-primary); - color: @brand-primary; - - &:hover { - border: none; - color: @brand-primary; - } - } - - & > li.disabled > a { - .box-shadow(inset 0 -1px 0 #ddd); - } - - &.nav-justified { - - & > li > a, - & > li > a:hover, - & > li > a:focus, - & > .active > a, - & > .active > a:hover, - & > .active > a:focus { - border: none; - } - } - - .dropdown-menu { - margin-top: 0; - } -} - -.dropdown-menu { - margin-top: 0; - border: none; - .box-shadow(0 1px 4px rgba(0,0,0,.3)); -} - -// Indicators ================================================================= - -.alert { - border: none; - color: #fff; - - &-success { - background-color: @brand-success; - } - - &-info { - background-color: @brand-info; - } - - &-warning { - background-color: @brand-warning; - } - - &-danger { - background-color: @brand-danger; - } - - a:not(.close), - .alert-link { - color: #fff; - font-weight: bold; - } - - .close { - color: #fff; - } -} - -.badge { - padding: 4px 6px 4px; -} - -.progress { - position: relative; - z-index: 1; - height: 6px; - border-radius: 0; - - .box-shadow(none); - - &-bar { - .box-shadow(none); - - &:last-child { - border-radius: 0 3px 3px 0; - } - - &:last-child { - &:before { - display: block; - content: ""; - position: absolute; - width: 100%; - height: 100%; - left: 0; - right: 0; - z-index: -1; - background-color: lighten(@progress-bar-bg, 35%); - } - } - - &-success:last-child.progress-bar:before { - background-color: lighten(@brand-success, 35%); - } - - &-info:last-child.progress-bar:before { - background-color: lighten(@brand-info, 45%); - } - &-warning:last-child.progress-bar:before { - background-color: lighten(@brand-warning, 35%); - } - - &-danger:last-child.progress-bar:before { - background-color: lighten(@brand-danger, 25%); - } - } -} - -// Progress bars ============================================================== - -// Containers ================================================================= - -.close { - font-size: 34px; - font-weight: 300; - line-height: 24px; - opacity: 0.6; - .transition(all 0.2s); - - &:hover { - opacity: 1; - } -} - -.list-group { - - &-item { - padding: 15px; - } - - &-item-text { - color: @gray-light; - } -} - -.well { - border-radius: 0; - .box-shadow(none); -} - -.panel { - border: none; - border-radius: 2px; - .box-shadow(0 1px 4px rgba(0,0,0,.3)); - - &-heading { - border-bottom: none; - } - - &-footer { - border-top: none; - } -} - -.popover { - border: none; - .box-shadow(0 1px 4px rgba(0,0,0,.3)); -} - -.carousel { - &-caption { - h1, h2, h3, h4, h5, h6 { - color: inherit; - } - } -} - diff --git a/panoramix/assets/stylesheets/less/index.less b/panoramix/assets/stylesheets/less/index.less deleted file mode 100644 index c779c3ebe698..000000000000 --- a/panoramix/assets/stylesheets/less/index.less +++ /dev/null @@ -1,5 +0,0 @@ -// Index .less, any imports here will be included in the final css build - -@import "~bootstrap/less/bootstrap.less"; -@import "./variables.less"; -@import "./bootswatch.less"; diff --git a/panoramix/assets/stylesheets/less/variables.less b/panoramix/assets/stylesheets/less/variables.less deleted file mode 100644 index 305e9926f61f..000000000000 --- a/panoramix/assets/stylesheets/less/variables.less +++ /dev/null @@ -1,881 +0,0 @@ -// Modified from Bootswatch Paper 3.3.6 -// Variables -// -------------------------------------------------- - - -//== Colors -// -//## Airbnb colors -@rausch: #ff5a5f; // coral -@kazan: #007a87; // dark teal -@hackberry: #7b0051; // purple -@babu: #00d1c1; // light teal -@lima: #8ce071; // bright green -@beach: #ffb400; // yellow -@ebisu: #ffaa91; // peach -@tirol: #b4a76c; // khaki -@foggy: #9CA299; // dark grey -@hof: #565A5C; // light grey - -//## Gray and brand colors for use across Bootstrap. - -@gray-base: #000; -@gray-darker: lighten(@gray-base, 13.5%); // #222 -@gray-dark: #212121; -@gray: #666; -@gray-light: #bbb; -@gray-lighter: lighten(@gray-base, 93.5%); // #eee - -@brand-primary: darken(@babu, 5%); -@brand-success: darken(@lima, 15%); -@brand-info: @beach; -@brand-warning: @hackberry; -@brand-danger: darken(@rausch, 5%); - - -//== Scaffolding -// -//## Settings for some of the most global styles. - -//** Background color for ``. -@body-bg: #fff; -//** Global text color on ``. -@text-color: @gray; - -//** Global textual link color. -@link-color: @brand-primary; -//** Link hover color set via `darken()` function. -@link-hover-color: darken(@link-color, 15%); -//** Link hover decoration. -@link-hover-decoration: underline; - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -@font-family-sans-serif: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; -@font-family-serif: Georgia, "Times New Roman", Times, serif; -//** Default monospace fonts for ``, ``, and `
`.
-@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
-@font-family-base:        @font-family-sans-serif;
-
-@font-size-base:          13px;
-@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
-@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
-
-@font-size-h1:            56px;
-@font-size-h2:            45px;
-@font-size-h3:            34px;
-@font-size-h4:            24px;
-@font-size-h5:            20px;
-@font-size-h6:            14px;
-
-//** Unit-less `line-height` for use in components like buttons.
-@line-height-base:        1.846; // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
-
-//** By default, this inherits from the ``.
-@headings-font-family:    inherit;
-@headings-font-weight:    400;
-@headings-line-height:    1.1;
-@headings-color:          #444;
-
-
-//== Iconography
-//
-//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-//** Load fonts from this directory.
-@icon-font-path:          "../fonts/";
-//** File name for all font files.
-@icon-font-name:          "glyphicons-halflings-regular";
-//** Element ID within SVG icon file.
-@icon-font-svg-id:        "glyphicons_halflingsregular";
-
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-@padding-base-vertical:     6px;
-@padding-base-horizontal:   16px;
-
-@padding-large-vertical:    10px;
-@padding-large-horizontal:  16px;
-
-@padding-small-vertical:    5px;
-@padding-small-horizontal:  10px;
-
-@padding-xs-vertical:       1px;
-@padding-xs-horizontal:     5px;
-
-@line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
-@line-height-small:         1.5;
-
-@border-radius-base:        3px;
-@border-radius-large:       3px;
-@border-radius-small:       3px;
-
-//** Global color for active items (e.g., navs or dropdowns).
-@component-active-color:    #fff;
-//** Global background color for active items (e.g., navs or dropdowns).
-@component-active-bg:       @brand-primary;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-@caret-width-base:          4px;
-//** Carets increase slightly in size for larger components.
-@caret-width-large:         5px;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-@table-cell-padding:            8px;
-//** Padding for cells in `.table-condensed`.
-@table-condensed-cell-padding:  5px;
-
-//** Default background color used for all tables.
-@table-bg:                      transparent;
-//** Background color used for `.table-striped`.
-@table-bg-accent:               #f9f9f9;
-//** Background color used for `.table-hover`.
-@table-bg-hover:                @gray-lighter;
-@table-bg-active:               @table-bg-hover;
-
-//** Border color for table and cell borders.
-@table-border-color:            #ddd;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-@btn-font-weight:                normal;
-
-@btn-default-color:              #444;
-@btn-default-bg:                 #fff;
-@btn-default-border:             transparent;
-
-@btn-primary-color:              #fff;
-@btn-primary-bg:                 @brand-primary;
-@btn-primary-border:             transparent;
-
-@btn-success-color:              #fff;
-@btn-success-bg:                 @brand-success;
-@btn-success-border:             transparent;
-
-@btn-info-color:                 #fff;
-@btn-info-bg:                    @brand-info;
-@btn-info-border:                transparent;
-
-@btn-warning-color:              #fff;
-@btn-warning-bg:                 @brand-warning;
-@btn-warning-border:             transparent;
-
-@btn-danger-color:               #fff;
-@btn-danger-bg:                  @brand-danger;
-@btn-danger-border:              transparent;
-
-@btn-link-disabled-color:        @gray-light;
-
-// Allows for customizing button radius independently from global border radius
-@btn-border-radius-base:         @border-radius-base;
-@btn-border-radius-large:        @border-radius-large;
-@btn-border-radius-small:        @border-radius-small;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-@input-bg:                       transparent;
-//** `` background color
-@input-bg-disabled:              transparent;
-
-//** Text color for ``s
-@input-color:                    @gray;
-//** `` border color
-@input-border:                   transparent;
-
-// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
-//** Default `.form-control` border radius
-// This has no effect on ``s in CSS.
-@input-border-radius:            @border-radius-base;
-//** Large `.form-control` border radius
-@input-border-radius-large:      @border-radius-large;
-//** Small `.form-control` border radius
-@input-border-radius-small:      @border-radius-small;
-
-//** Border color for inputs on focus
-@input-border-focus:             #66afe9;
-
-//** Placeholder text color
-@input-color-placeholder:        @gray-light;
-
-//** Default `.form-control` height
-@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
-//** Large `.form-control` height
-@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
-//** Small `.form-control` height
-@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
-
-//** `.form-group` margin
-@form-group-margin-bottom:       15px;
-
-@legend-color:                   @gray-dark;
-@legend-border-color:            #e5e5e5;
-
-//** Background color for textual input addons
-@input-group-addon-bg:           transparent;
-//** Border color for textual input addons
-@input-group-addon-border-color: @input-border;
-
-//** Disabled cursor for form controls and buttons.
-@cursor-disabled:                not-allowed;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-@dropdown-bg:                    #fff;
-//** Dropdown menu `border-color`.
-@dropdown-border:                rgba(0,0,0,.15);
-//** Dropdown menu `border-color` **for IE8**.
-@dropdown-fallback-border:       #ccc;
-//** Divider color for between dropdown items.
-@dropdown-divider-bg:            #e5e5e5;
-
-//** Dropdown link text color.
-@dropdown-link-color:            @text-color;
-//** Hover color for dropdown links.
-@dropdown-link-hover-color:      darken(@gray-dark, 5%);
-//** Hover background for dropdown links.
-@dropdown-link-hover-bg:         @gray-lighter;
-
-//** Active dropdown menu item text color.
-@dropdown-link-active-color:     @component-active-color;
-//** Active dropdown menu item background color.
-@dropdown-link-active-bg:        @component-active-bg;
-
-//** Disabled dropdown menu item background color.
-@dropdown-link-disabled-color:   @gray-light;
-
-//** Text color for headers within dropdown menus.
-@dropdown-header-color:          @gray-light;
-
-//** Deprecated `@dropdown-caret-color` as of v3.1.0
-@dropdown-caret-color:           @gray-light;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-@zindex-navbar:            1000;
-@zindex-dropdown:          1000;
-@zindex-popover:           1060;
-@zindex-tooltip:           1070;
-@zindex-navbar-fixed:      1030;
-@zindex-modal-background:  1040;
-@zindex-modal:             1050;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-//** Deprecated `@screen-xs` as of v3.0.1
-@screen-xs:                  480px;
-//** Deprecated `@screen-xs-min` as of v3.2.0
-@screen-xs-min:              @screen-xs;
-//** Deprecated `@screen-phone` as of v3.0.1
-@screen-phone:               @screen-xs-min;
-
-// Small screen / tablet
-//** Deprecated `@screen-sm` as of v3.0.1
-@screen-sm:                  768px;
-@screen-sm-min:              @screen-sm;
-//** Deprecated `@screen-tablet` as of v3.0.1
-@screen-tablet:              @screen-sm-min;
-
-// Medium screen / desktop
-//** Deprecated `@screen-md` as of v3.0.1
-@screen-md:                  992px;
-@screen-md-min:              @screen-md;
-//** Deprecated `@screen-desktop` as of v3.0.1
-@screen-desktop:             @screen-md-min;
-
-// Large screen / wide desktop
-//** Deprecated `@screen-lg` as of v3.0.1
-@screen-lg:                  1200px;
-@screen-lg-min:              @screen-lg;
-//** Deprecated `@screen-lg-desktop` as of v3.0.1
-@screen-lg-desktop:          @screen-lg-min;
-
-// So media queries don't overlap when required, provide a maximum
-@screen-xs-max:              (@screen-sm-min - 1);
-@screen-sm-max:              (@screen-md-min - 1);
-@screen-md-max:              (@screen-lg-min - 1);
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-@grid-columns:              12;
-//** Padding between columns. Gets divided in half for the left and right.
-@grid-gutter-width:         30px;
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-@grid-float-breakpoint:     @screen-sm-min;
-//** Point at which the navbar begins collapsing.
-@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-@container-tablet:             (720px + @grid-gutter-width);
-//** For `@screen-sm-min` and up.
-@container-sm:                 @container-tablet;
-
-// Medium screen / desktop
-@container-desktop:            (940px + @grid-gutter-width);
-//** For `@screen-md-min` and up.
-@container-md:                 @container-desktop;
-
-// Large screen / wide desktop
-@container-large-desktop:      (1140px + @grid-gutter-width);
-//** For `@screen-lg-min` and up.
-@container-lg:                 @container-large-desktop;
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-@navbar-height:                    64px;
-@navbar-margin-bottom:             @line-height-computed;
-@navbar-border-radius:             @border-radius-base;
-@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
-@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
-@navbar-collapse-max-height:       340px;
-
-@navbar-default-color:             @gray-light;
-@navbar-default-bg:                #fff;
-@navbar-default-border:            transparent;
-
-// Navbar links
-@navbar-default-link-color:                @gray;
-@navbar-default-link-hover-color:          @gray-dark;
-@navbar-default-link-hover-bg:             transparent;
-@navbar-default-link-active-color:         @gray-dark;
-@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
-@navbar-default-link-disabled-color:       #ccc;
-@navbar-default-link-disabled-bg:          transparent;
-
-// Navbar brand label
-@navbar-default-brand-color:               @navbar-default-link-color;
-@navbar-default-brand-hover-color:         @navbar-default-link-hover-color;
-@navbar-default-brand-hover-bg:            transparent;
-
-// Navbar toggle
-@navbar-default-toggle-hover-bg:           transparent;
-@navbar-default-toggle-icon-bar-bg:        rgba(0,0,0,0.5);
-@navbar-default-toggle-border-color:       transparent;
-
-
-//=== Inverted navbar
-// Reset inverted navbar basics
-@navbar-inverse-color:                      @gray-light;
-@navbar-inverse-bg:                         @brand-primary;
-@navbar-inverse-border:                     transparent;
-
-// Inverted navbar links
-@navbar-inverse-link-color:                 lighten(@brand-primary, 30%);
-@navbar-inverse-link-hover-color:           #fff;
-@navbar-inverse-link-hover-bg:              transparent;
-@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
-@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
-@navbar-inverse-link-disabled-color:        #444;
-@navbar-inverse-link-disabled-bg:           transparent;
-
-// Inverted navbar brand label
-@navbar-inverse-brand-color:                @navbar-inverse-link-color;
-@navbar-inverse-brand-hover-color:          #fff;
-@navbar-inverse-brand-hover-bg:             transparent;
-
-// Inverted navbar toggle\
-@navbar-inverse-toggle-hover-bg:            transparent;
-@navbar-inverse-toggle-icon-bar-bg:         rgba(0,0,0,0.5);
-@navbar-inverse-toggle-border-color:        transparent;
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-@nav-link-padding:                          10px 15px;
-@nav-link-hover-bg:                         @gray-lighter;
-
-@nav-disabled-link-color:                   @gray-light;
-@nav-disabled-link-hover-color:             @gray-light;
-
-//== Tabs
-@nav-tabs-border-color:                     transparent;
-
-@nav-tabs-link-hover-border-color:          @gray-lighter;
-
-@nav-tabs-active-link-hover-bg:             transparent;
-@nav-tabs-active-link-hover-color:          @gray;
-@nav-tabs-active-link-hover-border-color:   transparent;
-
-@nav-tabs-justified-link-border-color:            @nav-tabs-border-color;
-@nav-tabs-justified-active-link-border-color:     @body-bg;
-
-//== Pills
-@nav-pills-border-radius:                   @border-radius-base;
-@nav-pills-active-link-hover-bg:            @component-active-bg;
-@nav-pills-active-link-hover-color:         @component-active-color;
-
-
-//== Pagination
-//
-//##
-
-@pagination-color:                     @link-color;
-@pagination-bg:                        #fff;
-@pagination-border:                    #ddd;
-
-@pagination-hover-color:               @link-hover-color;
-@pagination-hover-bg:                  @gray-lighter;
-@pagination-hover-border:              #ddd;
-
-@pagination-active-color:              #fff;
-@pagination-active-bg:                 @brand-primary;
-@pagination-active-border:             @brand-primary;
-
-@pagination-disabled-color:            @gray-light;
-@pagination-disabled-bg:               #fff;
-@pagination-disabled-border:           #ddd;
-
-
-//== Pager
-//
-//##
-
-@pager-bg:                             @pagination-bg;
-@pager-border:                         @pagination-border;
-@pager-border-radius:                  15px;
-
-@pager-hover-bg:                       @pagination-hover-bg;
-
-@pager-active-bg:                      @pagination-active-bg;
-@pager-active-color:                   @pagination-active-color;
-
-@pager-disabled-color:                 @pagination-disabled-color;
-
-
-//== Jumbotron
-//
-//##
-
-@jumbotron-padding:              30px;
-@jumbotron-color:                inherit;
-@jumbotron-bg:                   #f9f9f9;
-@jumbotron-heading-color:        @headings-color;
-@jumbotron-font-size:            ceil((@font-size-base * 1.5));
-@jumbotron-heading-font-size:    ceil((@font-size-base * 4.5));
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-@state-success-text:             @brand-success;
-@state-success-bg:               #dff0d8;
-@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
-
-@state-info-text:                @brand-info;
-@state-info-bg:                  #e1bee7;
-@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
-
-@state-warning-text:             @brand-warning;
-@state-warning-bg:               #ffe0b2;
-@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
-
-@state-danger-text:              @brand-danger;
-@state-danger-bg:                #f9bdbb;
-@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-@tooltip-max-width:           200px;
-//** Tooltip text color
-@tooltip-color:               #fff;
-//** Tooltip background color
-@tooltip-bg:                  #727272;
-@tooltip-opacity:             .9;
-
-//** Tooltip arrow width
-@tooltip-arrow-width:         5px;
-//** Tooltip arrow color
-@tooltip-arrow-color:         @tooltip-bg;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-@popover-bg:                          #fff;
-//** Popover maximum width
-@popover-max-width:                   276px;
-//** Popover border color
-@popover-border-color:                transparent;
-//** Popover fallback border color
-@popover-fallback-border-color:       transparent;
-
-//** Popover title background color
-@popover-title-bg:                    darken(@popover-bg, 3%);
-
-//** Popover arrow width
-@popover-arrow-width:                 10px;
-//** Popover arrow color
-@popover-arrow-color:                 @popover-bg;
-
-//** Popover outer arrow width
-@popover-arrow-outer-width:           (@popover-arrow-width + 1);
-//** Popover outer arrow color
-@popover-arrow-outer-color:           fadein(@popover-border-color, 7.5%);
-//** Popover outer arrow fallback color
-@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-@label-default-bg:            @gray-light;
-//** Primary label background color
-@label-primary-bg:            @brand-primary;
-//** Success label background color
-@label-success-bg:            @brand-success;
-//** Info label background color
-@label-info-bg:               @brand-info;
-//** Warning label background color
-@label-warning-bg:            @brand-warning;
-//** Danger label background color
-@label-danger-bg:             @brand-danger;
-
-//** Default label text color
-@label-color:                 #fff;
-//** Default text color of a linked label
-@label-link-hover-color:      #fff;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-@modal-inner-padding:         15px;
-
-//** Padding applied to the modal title
-@modal-title-padding:         15px;
-//** Modal title line-height
-@modal-title-line-height:     @line-height-base;
-
-//** Background color of modal content area
-@modal-content-bg:                             #fff;
-//** Modal content border color
-@modal-content-border-color:                   transparent;
-//** Modal content border color **for IE8**
-@modal-content-fallback-border-color:          #999;
-
-//** Modal backdrop background color
-@modal-backdrop-bg:           #000;
-//** Modal backdrop opacity
-@modal-backdrop-opacity:      .5;
-//** Modal header border color
-@modal-header-border-color:   transparent;
-//** Modal footer border color
-@modal-footer-border-color:   @modal-header-border-color;
-
-@modal-lg:                    900px;
-@modal-md:                    600px;
-@modal-sm:                    300px;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-@alert-padding:               15px;
-@alert-border-radius:         @border-radius-base;
-@alert-link-font-weight:      bold;
-
-@alert-success-bg:            @state-success-bg;
-@alert-success-text:          @state-success-text;
-@alert-success-border:        @state-success-border;
-
-@alert-info-bg:               @state-info-bg;
-@alert-info-text:             @state-info-text;
-@alert-info-border:           @state-info-border;
-
-@alert-warning-bg:            @state-warning-bg;
-@alert-warning-text:          @state-warning-text;
-@alert-warning-border:        @state-warning-border;
-
-@alert-danger-bg:             @state-danger-bg;
-@alert-danger-text:           @state-danger-text;
-@alert-danger-border:         @state-danger-border;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-@progress-bg:                 #f5f5f5;
-//** Progress bar text color
-@progress-bar-color:          #fff;
-//** Variable for setting rounded corners on progress bar.
-@progress-border-radius:      @border-radius-base;
-
-//** Default progress bar color
-@progress-bar-bg:             @brand-primary;
-//** Success progress bar color
-@progress-bar-success-bg:     @brand-success;
-//** Warning progress bar color
-@progress-bar-warning-bg:     @brand-warning;
-//** Danger progress bar color
-@progress-bar-danger-bg:      @brand-danger;
-//** Info progress bar color
-@progress-bar-info-bg:        @brand-info;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-@list-group-bg:                 #fff;
-//** `.list-group-item` border color
-@list-group-border:             #ddd;
-//** List group border radius
-@list-group-border-radius:      @border-radius-base;
-
-//** Background color of single list items on hover
-@list-group-hover-bg:           #f5f5f5;
-//** Text color of active list items
-@list-group-active-color:       @component-active-color;
-//** Background color of active list items
-@list-group-active-bg:          @component-active-bg;
-//** Border color of active list elements
-@list-group-active-border:      @list-group-active-bg;
-//** Text color for content within active list items
-@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
-
-//** Text color of disabled list items
-@list-group-disabled-color:      @gray-light;
-//** Background color of disabled list items
-@list-group-disabled-bg:         @gray-lighter;
-//** Text color for content within disabled list items
-@list-group-disabled-text-color: @list-group-disabled-color;
-
-@list-group-link-color:         #555;
-@list-group-link-hover-color:   @list-group-link-color;
-@list-group-link-heading-color: #333;
-
-
-//== Panels
-//
-//##
-
-@panel-bg:                    #fff;
-@panel-body-padding:          15px;
-@panel-heading-padding:       10px 15px;
-@panel-footer-padding:        @panel-heading-padding;
-@panel-border-radius:         @border-radius-base;
-
-//** Border color for elements within panels
-@panel-inner-border:          #ddd;
-@panel-footer-bg:             #f5f5f5;
-
-@panel-default-text:          @gray-dark;
-@panel-default-border:        #ddd;
-@panel-default-heading-bg:    #f5f5f5;
-
-@panel-primary-text:          #fff;
-@panel-primary-border:        @brand-primary;
-@panel-primary-heading-bg:    @brand-primary;
-
-@panel-success-text:          #fff;
-@panel-success-border:        @state-success-border;
-@panel-success-heading-bg:    @brand-success;
-
-@panel-info-text:             #fff;
-@panel-info-border:           @state-info-border;
-@panel-info-heading-bg:       @brand-info;
-
-@panel-warning-text:          #fff;
-@panel-warning-border:        @state-warning-border;
-@panel-warning-heading-bg:    @brand-warning;
-
-@panel-danger-text:           #fff;
-@panel-danger-border:         @state-danger-border;
-@panel-danger-heading-bg:     @brand-danger;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-@thumbnail-padding:           4px;
-//** Thumbnail background color
-@thumbnail-bg:                @body-bg;
-//** Thumbnail border color
-@thumbnail-border:            #ddd;
-//** Thumbnail border radius
-@thumbnail-border-radius:     @border-radius-base;
-
-//** Custom text color for thumbnail captions
-@thumbnail-caption-color:     @text-color;
-//** Padding around the thumbnail caption
-@thumbnail-caption-padding:   9px;
-
-
-//== Wells
-//
-//##
-
-@well-bg:                     #f9f9f9;
-@well-border:                 transparent;
-
-
-//== Badges
-//
-//##
-
-@badge-color:                 #fff;
-//** Linked badge text color on hover
-@badge-link-hover-color:      #fff;
-@badge-bg:                    @gray-light;
-
-//** Badge text color in active nav link
-@badge-active-color:          @link-color;
-//** Badge background color in active nav link
-@badge-active-bg:             #fff;
-
-@badge-font-weight:           normal;
-@badge-line-height:           1;
-@badge-border-radius:         10px;
-
-
-//== Breadcrumbs
-//
-//##
-
-@breadcrumb-padding-vertical:   8px;
-@breadcrumb-padding-horizontal: 15px;
-//** Breadcrumb background color
-@breadcrumb-bg:                 #f5f5f5;
-//** Breadcrumb text color
-@breadcrumb-color:              #ccc;
-//** Text color of current page in the breadcrumb
-@breadcrumb-active-color:       @gray-light;
-//** Textual separator for between breadcrumb elements
-@breadcrumb-separator:          "/";
-
-
-//== Carousel
-//
-//##
-
-@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
-
-@carousel-control-color:                      #fff;
-@carousel-control-width:                      15%;
-@carousel-control-opacity:                    .5;
-@carousel-control-font-size:                  20px;
-
-@carousel-indicator-active-bg:                #fff;
-@carousel-indicator-border-color:             #fff;
-
-@carousel-caption-color:                      #fff;
-
-
-//== Close
-//
-//##
-
-@close-font-weight:           normal;
-@close-color:                 #000;
-@close-text-shadow:           none;
-
-
-//== Code
-//
-//##
-
-@code-color:                  #c7254e;
-@code-bg:                     #f9f2f4;
-
-@kbd-color:                   #fff;
-@kbd-bg:                      #333;
-
-@pre-bg:                      #f5f5f5;
-@pre-color:                   @gray-dark;
-@pre-border-color:            #ccc;
-@pre-scrollable-max-height:   340px;
-
-
-//== Type
-//
-//##
-
-//** Horizontal offset for forms and lists.
-@component-offset-horizontal: 180px;
-//** Text muted color
-@text-muted:                  @gray-light;
-//** Abbreviations and acronyms border color
-@abbr-border-color:           @gray-light;
-//** Headings small color
-@headings-small-color:        @gray-light;
-//** Blockquote small color
-@blockquote-small-color:      @gray-light;
-//** Blockquote font size
-@blockquote-font-size:        (@font-size-base * 1.25);
-//** Blockquote border color
-@blockquote-border-color:     @gray-lighter;
-//** Page header border color
-@page-header-border-color:    @gray-lighter;
-//** Width of horizontal description list titles
-@dl-horizontal-offset:        @component-offset-horizontal;
-//** Point at which .dl-horizontal becomes horizontal
-@dl-horizontal-breakpoint:    @grid-float-breakpoint;
-//** Horizontal line color.
-@hr-border:                   @gray-lighter;
diff --git a/panoramix/assets/stylesheets/panoramix.css b/panoramix/assets/stylesheets/panoramix.css
deleted file mode 100644
index 6e5a8225613f..000000000000
--- a/panoramix/assets/stylesheets/panoramix.css
+++ /dev/null
@@ -1,244 +0,0 @@
-body {
-    margin: 0px !important;
-}
-
-.modal-dialog {
-    z-index: 1100;
-}
-
-input.form-control {
-    background-color: white;
-}
-
-.col-left-fixed {
-    width:350px;
-    position: absolute;
-    float: left;
-}
-.col-offset {
-    margin-left: 365px;
-}
-
-.slice_description{
-    padding: 8px;
-    margin: 5px;
-    border: 1px solid #DDD;
-    background-color: #F8F8F8;
-    border-radius: 5px;
-    font-size: 12px;
-}
-
-.slice_info{
-    cursor: pointer;
-}
-
-.padded {
-    padding: 10px;
-}
-
-.intable-longtext{
-    max-height: 200px;
-    overflow: auto;
-}
-
-.container-fluid {
-    text-align: left;
-}
-input[type="checkbox"] {
-  display: inline-block;
-  width: 16px;
-  height: 16px;
-  float: right;
-}
-form div {
-  padding-top: 1px;
-}
-.navbar-brand a {
-  color: white;
-}
-
-.header span{
-    margin-left: 3px;
-}
-
-#timer {
-    width: 80px;
-    text-align: right;
-}
-
-.notbtn {
-    cursor: default;
-}
-hr {
-    margin-top: 15px;
-    margin-bottom: 15px;
-}
-
-span.title-block {
-    background-color: #EEE;
-    border-radius: 4px;
-    padding: 6px 12px;
-    margin: 0px 10px;
-    font-size: 20px;
-}
-
-fieldset.fs-style {
-    font-family: Verdana, Arial, sans-serif;
-    font-size: small;
-    font-weight: normal;
-    border: 1px solid #CCC;
-    background-color: #F4F4F4;
-    border-radius: 6px;
-    padding: 10px;
-    margin: 0px 0px 10px 0px;
-}
-legend.legend-style {
-    font-size: 14px;
-    padding: 0px 6px;
-    cursor: pointer;
-    margin: 0px;
-    color: #444;
-    background-color: transparent;
-    font-weight: bold;
-}
-.nvtooltip {
-    //position: relative !important;
-    z-index: 888;
-}
-.nvtooltip table td{
-    font-size: 11px !important;
-}
-legend {
-    width: auto;
-    border-bottom: 0px;
-}
-.navbar {
-  -webkit-box-shadow: 0px 3px 3px #AAA;
-  -moz-box-shadow: 0px 3px 3px #AAA;
-  box-shadow: 0px 3px 3px #AAA;
-  z-index: 999;
-}
-.panel.panel-primary {
-  margin: 10px;
-}
-
-.index .carousel img {
-  max-height: 500px;
-}
-.index .carousel {
-  overflow: hidden;
-  height: 500px;
-}
-.index .carousel-caption h1 {
-  font-size: 80px;
-}
-.index .carousel-caption p {
-  font-size: 20px;
-}
-.index div.carousel-caption{
-  background: rgba(0,0,0,0.5);
-  border-radius: 20px;
-  top: 150px;
-  bottom: auto !important;
-}
-.index .carousel-inner > .item > img {
-  margin: 0 auto;
-}
-.index {
-  margin: -20px;
-}
-.index .carousel-indicators li {
-  background-color: #AAA;
-  border: 1px solid black;
-}
-
-.index .carousel-indicators .active {
-  background-color: #000;
-  border: 5px solid black;
-}
-
-.datasource form div.form-control {
-  margin-bottom: 5px !important;
-}
-.datasource form input.form-control {
-  margin-bottom: 5px !important;
-}
-.datasource .tooltip-inner {
-  max-width: 350px;
-}
-img.loading {
-  width: 40px;
-}
-
-.dashboard a i {
-  cursor: pointer;
-}
-.dashboard i.drag {
-  cursor: move !important;
-}
-.dashboard .gridster .preview-holder {
-  z-index: 1;
-  position: absolute;
-  background-color: #AAA;
-  border-color: #AAA;
-  opacity: 0.3;
-}
-.gridster li.widget{
-  list-style-type: none;
-  border-radius: 0;
-  margin: 5px;
-  border: 1px solid #ccc;
-  box-shadow: 2px 1px 5px -2px #aaa;
-  overflow: hidden;
-  background-color: #fff;
-}
-.dashboard .gridster .dragging,
-.dashboard .gridster .resizing {
-  opacity: 0.5;
-}
-.dashboard img.loading {
-  width: 20px;
-  margin: 5px;
-}
-.dashboard .title {
-  text-align: center;
-}
-.dashboard .slice_title {
-  text-align: center;
-  font-weight: bold;
-  font-size: 14px;
-  padding: 5px;
-}
-.dashboard div.slice_content {
-  width: 100%;
-  height: 100%;
-}
-
-.dashboard  div.nvtooltip {
-  z-index: 888;  /* this lets tool tips go on top of other slices  */
-}
-
-div.header {
-  font-weight: bold;
-}
-li.widget:hover {
-  z-index: 1000;
-}
-
-li.widget .chart-header {
-  padding: 5px;
-  background-color: #f1f1f1;
-}
-
-li.widget .chart-header a {
-  margin-left: 5px;
-}
-
-li.widget .chart-controls {
-  display: none;
-  background-color: #f1f1f1;
-}
-
-li.widget .slice_container {
-  overflow: auto;
-}
diff --git a/panoramix/assets/vendor/parallel_coordinates/d3.parcoords.css b/panoramix/assets/vendor/parallel_coordinates/d3.parcoords.css
deleted file mode 100644
index b53849c3673b..000000000000
--- a/panoramix/assets/vendor/parallel_coordinates/d3.parcoords.css
+++ /dev/null
@@ -1,71 +0,0 @@
-.parcoords svg, .parcoords canvas {
-  font-size: 12px;
-  position: absolute;
-}
-.parcoords > canvas {
-  pointer-events: none;
-}
-
-.parcoords text.label {
-  font: 100%;
-  font-size: 12px;
-  cursor: drag;
-}
-
-.parcoords rect.background {
-  fill: transparent;
-}
-.parcoords rect.background:hover {
-  fill: rgba(120,120,120,0.2);
-}
-.parcoords .resize rect {
-  fill: rgba(0,0,0,0.1);
-}
-.parcoords rect.extent {
-  fill: rgba(255,255,255,0.25);
-  stroke: rgba(0,0,0,0.6);
-}
-.parcoords .axis line, .parcoords .axis path {
-  fill: none;
-  stroke: #222;
-  shape-rendering: crispEdges;
-}
-.parcoords canvas {
-  opacity: 1;
-  -moz-transition: opacity 0.3s;
-  -webkit-transition: opacity 0.3s;
-  -o-transition: opacity 0.3s;
-}
-.parcoords canvas.faded {
-  opacity: 0.25;
-}
-.parcoords {
-	-webkit-touch-callout: none;
-	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: none;
-	-ms-user-select: none;
-	user-select: none;
-    background-color: white;
-}
-
-/* data table styles */
-.parcoords .row, .parcoords .header {
-    clear: left; font-size: 12px; line-height: 18px; height: 18px;
-    margin: 0px;
-}
-.parcoords .row:nth-child(odd) {
-  background: rgba(0,0,0,0.05);
-}
-.parcoords .header {
-  font-weight: bold;
-}
-.parcoords .cell {
-  float: left;
-  overflow: hidden;
-  white-space: nowrap;
-  width: 100px; height: 18px;
-}
-.parcoords .col-0 {
-  width: 180px;
-}
diff --git a/panoramix/assets/vendor/parallel_coordinates/d3.parcoords.js b/panoramix/assets/vendor/parallel_coordinates/d3.parcoords.js
deleted file mode 100644
index 04095f106e4a..000000000000
--- a/panoramix/assets/vendor/parallel_coordinates/d3.parcoords.js
+++ /dev/null
@@ -1,2224 +0,0 @@
-module.exports = function(config) {
-  var __ = {
-    data: [],
-    highlighted: [],
-    dimensions: [],
-    dimensionTitles: {},
-    dimensionTitleRotation: 0,
-    types: {},
-    brushed: false,
-    brushedColor: null,
-    alphaOnBrushed: 0.0,
-    mode: "default",
-    rate: 20,
-    width: 600,
-    height: 300,
-    margin: { top: 24, right: 0, bottom: 12, left: 0 },
-    nullValueSeparator: "undefined", // set to "top" or "bottom"
-    nullValueSeparatorPadding: { top: 8, right: 0, bottom: 8, left: 0 },
-    color: "#069",
-    composite: "source-over",
-    alpha: 0.7,
-    bundlingStrength: 0.5,
-    bundleDimension: null,
-    smoothness: 0.0,
-    showControlPoints: false,
-    hideAxis : []
-  };
-
-  extend(__, config);
-
-  var pc = function(selection) {
-    selection = pc.selection = d3.select(selection);
-
-    __.width = selection[0][0].clientWidth;
-    __.height = selection[0][0].clientHeight;
-
-    // canvas data layers
-    ["marks", "foreground", "brushed", "highlight"].forEach(function(layer) {
-      canvas[layer] = selection
-        .append("canvas")
-        .attr("class", layer)[0][0];
-      ctx[layer] = canvas[layer].getContext("2d");
-    });
-
-    // svg tick and brush layers
-    pc.svg = selection
-      .append("svg")
-        .attr("width", __.width)
-        .attr("height", __.height)
-      .append("svg:g")
-        .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
-
-    return pc;
-  };
-  var events = d3.dispatch.apply(this,["render", "resize", "highlight", "brush", "brushend", "axesreorder"].concat(d3.keys(__))),
-      w = function() { return __.width - __.margin.right - __.margin.left; },
-      h = function() { return __.height - __.margin.top - __.margin.bottom; },
-      flags = {
-        brushable: false,
-        reorderable: false,
-        axes: false,
-        interactive: false,
-        debug: false
-      },
-      xscale = d3.scale.ordinal(),
-      yscale = {},
-      dragging = {},
-      line = d3.svg.line(),
-      axis = d3.svg.axis().orient("left").ticks(5),
-      g, // groups for axes, brushes
-      ctx = {},
-      canvas = {},
-      clusterCentroids = [];
-
-  // side effects for setters
-  var side_effects = d3.dispatch.apply(this,d3.keys(__))
-    .on("composite", function(d) {
-      ctx.foreground.globalCompositeOperation = d.value;
-      ctx.brushed.globalCompositeOperation = d.value;
-    })
-    .on("alpha", function(d) {
-      ctx.foreground.globalAlpha = d.value;
-      ctx.brushed.globalAlpha = d.value;
-    })
-    .on("brushedColor", function (d) {
-      ctx.brushed.strokeStyle = d.value;
-    })
-    .on("width", function(d) { pc.resize(); })
-    .on("height", function(d) { pc.resize(); })
-    .on("margin", function(d) { pc.resize(); })
-    .on("rate", function(d) {
-      brushedQueue.rate(d.value);
-      foregroundQueue.rate(d.value);
-    })
-    .on("dimensions", function(d) {
-      xscale.domain(__.dimensions);
-      if (flags.interactive){pc.render().updateAxes();}
-    })
-    .on("bundleDimension", function(d) {
-      if (!__.dimensions.length) pc.detectDimensions();
-      if (!(__.dimensions[0] in yscale)) pc.autoscale();
-      if (typeof d.value === "number") {
-        if (d.value < __.dimensions.length) {
-          __.bundleDimension = __.dimensions[d.value];
-        } else if (d.value < __.hideAxis.length) {
-          __.bundleDimension = __.hideAxis[d.value];
-        }
-      } else {
-        __.bundleDimension = d.value;
-      }
-
-      __.clusterCentroids = compute_cluster_centroids(__.bundleDimension);
-    })
-    .on("hideAxis", function(d) {
-      if (!__.dimensions.length) pc.detectDimensions();
-      pc.dimensions(without(__.dimensions, d.value));
-    });
-
-  // expose the state of the chart
-  pc.state = __;
-  pc.flags = flags;
-
-  // create getter/setters
-  getset(pc, __, events);
-
-  // expose events
-  d3.rebind(pc, events, "on");
-
-  // getter/setter with event firing
-  function getset(obj,state,events)  {
-    d3.keys(state).forEach(function(key) {
-      obj[key] = function(x) {
-        if (!arguments.length) {
-      return state[key];
-    }
-        var old = state[key];
-        state[key] = x;
-        side_effects[key].call(pc,{"value": x, "previous": old});
-        events[key].call(pc,{"value": x, "previous": old});
-        return obj;
-      };
-    });
-  };
-
-  function extend(target, source) {
-    for (var key in source) {
-      target[key] = source[key];
-    }
-    return target;
-  };
-
-  function without(arr, item) {
-    return arr.filter(function(elem) { return item.indexOf(elem) === -1; })
-  };
-  /** adjusts an axis' default range [h()+1, 1] if a NullValueSeparator is set */
-  function getRange() {
-    if (__.nullValueSeparator=="bottom") {
-      return [h()+1-__.nullValueSeparatorPadding.bottom-__.nullValueSeparatorPadding.top, 1];
-    } else if (__.nullValueSeparator=="top") {
-      return [h()+1, 1+__.nullValueSeparatorPadding.bottom+__.nullValueSeparatorPadding.top];
-    }
-    return [h()+1, 1];
-  };
-
-  pc.autoscale = function() {
-    // yscale
-    var defaultScales = {
-      "date": function(k) {
-        var extent = d3.extent(__.data, function(d) {
-          return d[k] ? d[k].getTime() : null;
-        });
-
-        // special case if single value
-        if (extent[0] === extent[1]) {
-          return d3.scale.ordinal()
-            .domain([extent[0]])
-            .rangePoints(getRange());
-        }
-
-        return d3.time.scale()
-          .domain(extent)
-          .range(getRange());
-      },
-      "number": function(k) {
-        var extent = d3.extent(__.data, function(d) { return +d[k]; });
-
-        // special case if single value
-        if (extent[0] === extent[1]) {
-          return d3.scale.ordinal()
-            .domain([extent[0]])
-            .rangePoints(getRange());
-        }
-
-        return d3.scale.linear()
-          .domain(extent)
-          .range(getRange());
-      },
-      "string": function(k) {
-        var counts = {},
-            domain = [];
-
-        // Let's get the count for each value so that we can sort the domain based
-        // on the number of items for each value.
-        __.data.map(function(p) {
-          if (p[k] === undefined && __.nullValueSeparator!== "undefined"){
-            return; // null values will be drawn beyond the horizontal null value separator!
-          }
-          if (counts[p[k]] === undefined) {
-            counts[p[k]] = 1;
-          } else {
-            counts[p[k]] = counts[p[k]] + 1;
-          }
-        });
-
-        domain = Object.getOwnPropertyNames(counts).sort(function(a, b) {
-          return counts[a] - counts[b];
-        });
-
-        return d3.scale.ordinal()
-          .domain(domain)
-          .rangePoints(getRange());
-      }
-    };
-
-    __.dimensions.forEach(function(k) {
-      yscale[k] = defaultScales[__.types[k]](k);
-    });
-
-    __.hideAxis.forEach(function(k) {
-      yscale[k] = defaultScales[__.types[k]](k);
-    });
-
-    // xscale
-    xscale.rangePoints([0, w()], 1);
-
-    // canvas sizes
-    pc.selection.selectAll("canvas")
-        .style("margin-top", __.margin.top + "px")
-        .style("margin-left", __.margin.left + "px")
-        .attr("width", w()+2)
-        .attr("height", h()+2);
-
-    // default styles, needs to be set when canvas width changes
-    ctx.foreground.strokeStyle = __.color;
-    ctx.foreground.lineWidth = 1.4;
-    ctx.foreground.globalCompositeOperation = __.composite;
-    ctx.foreground.globalAlpha = __.alpha;
-    ctx.brushed.strokeStyle = __.brushedColor;
-    ctx.brushed.lineWidth = 1.4;
-    ctx.brushed.globalCompositeOperation = __.composite;
-    ctx.brushed.globalAlpha = __.alpha;
-    ctx.highlight.lineWidth = 3;
-
-    return this;
-  };
-
-  pc.scale = function(d, domain) {
-    yscale[d].domain(domain);
-
-    return this;
-  };
-
-  pc.flip = function(d) {
-    //yscale[d].domain().reverse();         // does not work
-    yscale[d].domain(yscale[d].domain().reverse()); // works
-
-    return this;
-  };
-
-  pc.commonScale = function(global, type) {
-    var t = type || "number";
-    if (typeof global === 'undefined') {
-      global = true;
-    }
-
-    // scales of the same type
-    var scales = __.dimensions.concat(__.hideAxis).filter(function(p) {
-      return __.types[p] == t;
-    });
-
-    if (global) {
-      var extent = d3.extent(scales.map(function(p,i) {
-          return yscale[p].domain();
-        }).reduce(function(a,b) {
-          return a.concat(b);
-        }));
-
-      scales.forEach(function(d) {
-        yscale[d].domain(extent);
-      });
-
-    } else {
-      scales.forEach(function(k) {
-        yscale[k].domain(d3.extent(__.data, function(d) { return +d[k]; }));
-      });
-    }
-
-    // update centroids
-    if (__.bundleDimension !== null) {
-      pc.bundleDimension(__.bundleDimension);
-    }
-
-    return this;
-  };
-  pc.detectDimensions = function() {
-    pc.types(pc.detectDimensionTypes(__.data));
-    pc.dimensions(d3.keys(pc.types()));
-    return this;
-  };
-
-  // a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
-  pc.toType = function(v) {
-    return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
-  };
-
-  // try to coerce to number before returning type
-  pc.toTypeCoerceNumbers = function(v) {
-    if ((parseFloat(v) == v) && (v != null)) {
-    return "number";
-  }
-    return pc.toType(v);
-  };
-
-  // attempt to determine types of each dimension based on first row of data
-  pc.detectDimensionTypes = function(data) {
-    var types = {};
-    d3.keys(data[0])
-      .forEach(function(col) {
-        types[col] = pc.toTypeCoerceNumbers(data[0][col]);
-      });
-    return types;
-  };
-  pc.render = function() {
-    // try to autodetect dimensions and create scales
-    if (!__.dimensions.length) pc.detectDimensions();
-    if (!(__.dimensions[0] in yscale)) pc.autoscale();
-
-    pc.render[__.mode]();
-
-    events.render.call(this);
-    return this;
-  };
-
-  pc.renderBrushed = function() {
-    if (!__.dimensions.length) pc.detectDimensions();
-    if (!(__.dimensions[0] in yscale)) pc.autoscale();
-
-    pc.renderBrushed[__.mode]();
-
-    events.render.call(this);
-    return this;
-  };
-
-  function isBrushed() {
-    if (__.brushed && __.brushed.length !== __.data.length)
-      return true;
-
-    var object = brush.currentMode().brushState();
-
-    for (var key in object) {
-      if (object.hasOwnProperty(key)) {
-        return true;
-      }
-    }
-    return false;
-  };
-
-  pc.render.default = function() {
-    pc.clear('foreground');
-    pc.clear('highlight');
-
-    pc.renderBrushed.default();
-
-    __.data.forEach(path_foreground);
-  };
-
-  var foregroundQueue = d3.renderQueue(path_foreground)
-    .rate(50)
-    .clear(function() {
-      pc.clear('foreground');
-      pc.clear('highlight');
-    });
-
-  pc.render.queue = function() {
-    pc.renderBrushed.queue();
-
-    foregroundQueue(__.data);
-  };
-
-  pc.renderBrushed.default = function() {
-    pc.clear('brushed');
-
-    if (isBrushed()) {
-      __.brushed.forEach(path_brushed);
-    }
-  };
-
-  var brushedQueue = d3.renderQueue(path_brushed)
-    .rate(50)
-    .clear(function() {
-      pc.clear('brushed');
-    });
-
-  pc.renderBrushed.queue = function() {
-    if (isBrushed()) {
-      brushedQueue(__.brushed);
-    } else {
-      brushedQueue([]); // This is needed to clear the currently brushed items
-    }
-  };
-  function compute_cluster_centroids(d) {
-
-    var clusterCentroids = d3.map();
-    var clusterCounts = d3.map();
-    // determine clusterCounts
-    __.data.forEach(function(row) {
-      var scaled = yscale[d](row[d]);
-      if (!clusterCounts.has(scaled)) {
-        clusterCounts.set(scaled, 0);
-      }
-      var count = clusterCounts.get(scaled);
-      clusterCounts.set(scaled, count + 1);
-    });
-
-    __.data.forEach(function(row) {
-      __.dimensions.map(function(p, i) {
-        var scaled = yscale[d](row[d]);
-        if (!clusterCentroids.has(scaled)) {
-          var map = d3.map();
-          clusterCentroids.set(scaled, map);
-        }
-        if (!clusterCentroids.get(scaled).has(p)) {
-          clusterCentroids.get(scaled).set(p, 0);
-        }
-        var value = clusterCentroids.get(scaled).get(p);
-        value += yscale[p](row[p]) / clusterCounts.get(scaled);
-        clusterCentroids.get(scaled).set(p, value);
-      });
-    });
-
-    return clusterCentroids;
-
-  }
-
-  function compute_centroids(row) {
-    var centroids = [];
-
-    var p = __.dimensions;
-    var cols = p.length;
-    var a = 0.5;      // center between axes
-    for (var i = 0; i < cols; ++i) {
-      // centroids on 'real' axes
-      var x = position(p[i]);
-      var y = yscale[p[i]](row[p[i]]);
-      centroids.push($V([x, y]));
-
-      // centroids on 'virtual' axes
-      if (i < cols - 1) {
-        var cx = x + a * (position(p[i+1]) - x);
-        var cy = y + a * (yscale[p[i+1]](row[p[i+1]]) - y);
-        if (__.bundleDimension !== null) {
-          var leftCentroid = __.clusterCentroids.get(yscale[__.bundleDimension](row[__.bundleDimension])).get(p[i]);
-          var rightCentroid = __.clusterCentroids.get(yscale[__.bundleDimension](row[__.bundleDimension])).get(p[i+1]);
-          var centroid = 0.5 * (leftCentroid + rightCentroid);
-          cy = centroid + (1 - __.bundlingStrength) * (cy - centroid);
-        }
-        centroids.push($V([cx, cy]));
-      }
-    }
-
-    return centroids;
-  }
-
-  function compute_control_points(centroids) {
-
-    var cols = centroids.length;
-    var a = __.smoothness;
-    var cps = [];
-
-    cps.push(centroids[0]);
-    cps.push($V([centroids[0].e(1) + a*2*(centroids[1].e(1)-centroids[0].e(1)), centroids[0].e(2)]));
-    for (var col = 1; col < cols - 1; ++col) {
-      var mid = centroids[col];
-      var left = centroids[col - 1];
-      var right = centroids[col + 1];
-
-      var diff = left.subtract(right);
-      cps.push(mid.add(diff.x(a)));
-      cps.push(mid);
-      cps.push(mid.subtract(diff.x(a)));
-    }
-    cps.push($V([centroids[cols-1].e(1) + a*2*(centroids[cols-2].e(1)-centroids[cols-1].e(1)), centroids[cols-1].e(2)]));
-    cps.push(centroids[cols - 1]);
-
-    return cps;
-
-  };
-
-  pc.shadows = function() {
-    flags.shadows = true;
-    pc.alphaOnBrushed(0.1);
-    pc.render();
-    return this;
-  };
-
-  // draw dots with radius r on the axis line where data intersects
-  pc.axisDots = function(r) {
-    var r = r || 0.1;
-    var ctx = pc.ctx.marks;
-    var startAngle = 0;
-    var endAngle = 2 * Math.PI;
-    ctx.globalAlpha = d3.min([ 1 / Math.pow(__.data.length, 1 / 2), 1 ]);
-    __.data.forEach(function(d) {
-      __.dimensions.map(function(p, i) {
-        ctx.beginPath();
-        ctx.arc(position(p), yscale[p](d[p]), r, startAngle, endAngle);
-        ctx.stroke();
-        ctx.fill();
-      });
-    });
-    return this;
-  };
-
-  // draw single cubic bezier curve
-  function single_curve(d, ctx) {
-
-    var centroids = compute_centroids(d);
-    var cps = compute_control_points(centroids);
-
-    ctx.moveTo(cps[0].e(1), cps[0].e(2));
-    for (var i = 1; i < cps.length; i += 3) {
-      if (__.showControlPoints) {
-        for (var j = 0; j < 3; j++) {
-          ctx.fillRect(cps[i+j].e(1), cps[i+j].e(2), 2, 2);
-        }
-      }
-      ctx.bezierCurveTo(cps[i].e(1), cps[i].e(2), cps[i+1].e(1), cps[i+1].e(2), cps[i+2].e(1), cps[i+2].e(2));
-    }
-  };
-
-  // draw single polyline
-  function color_path(d, ctx) {
-    ctx.beginPath();
-    if ((__.bundleDimension !== null && __.bundlingStrength > 0) || __.smoothness > 0) {
-      single_curve(d, ctx);
-    } else {
-      single_path(d, ctx);
-    }
-    ctx.stroke();
-  };
-
-  // draw many polylines of the same color
-  function paths(data, ctx) {
-    ctx.clearRect(-1, -1, w() + 2, h() + 2);
-    ctx.beginPath();
-    data.forEach(function(d) {
-      if ((__.bundleDimension !== null && __.bundlingStrength > 0) || __.smoothness > 0) {
-        single_curve(d, ctx);
-      } else {
-        single_path(d, ctx);
-      }
-    });
-    ctx.stroke();
-  };
-
-  // returns the y-position just beyond the separating null value line
-  function getNullPosition() {
-    if (__.nullValueSeparator=="bottom") {
-      return h()+1;
-    } else if (__.nullValueSeparator=="top") {
-      return 1;
-    } else {
-      console.log("A value is NULL, but nullValueSeparator is not set; set it to 'bottom' or 'top'.");
-    }
-    return h()+1;
-  };
-
-  function single_path(d, ctx) {
-    __.dimensions.map(function(p, i) {
-      if (i == 0) {
-        ctx.moveTo(position(p), typeof d[p] =='undefined' ? getNullPosition() : yscale[p](d[p]));
-      } else {
-        ctx.lineTo(position(p), typeof d[p] =='undefined' ? getNullPosition() : yscale[p](d[p]));
-      }
-    });
-  };
-
-  function path_brushed(d, i) {
-    if (__.brushedColor !== null) {
-      ctx.brushed.strokeStyle = d3.functor(__.brushedColor)(d, i);
-    } else {
-      ctx.brushed.strokeStyle = d3.functor(__.color)(d, i);
-    }
-    return color_path(d, ctx.brushed)
-  };
-
-  function path_foreground(d, i) {
-    ctx.foreground.strokeStyle = d3.functor(__.color)(d, i);
-    return color_path(d, ctx.foreground);
-  };
-
-  function path_highlight(d, i) {
-    ctx.highlight.strokeStyle = d3.functor(__.color)(d, i);
-    return color_path(d, ctx.highlight);
-  };
-  pc.clear = function(layer) {
-    ctx[layer].clearRect(0, 0, w() + 2, h() + 2);
-
-    // This will make sure that the foreground items are transparent
-    // without the need for changing the opacity style of the foreground canvas
-    // as this would stop the css styling from working
-    if(layer === "brushed" && isBrushed()) {
-      ctx.brushed.fillStyle = pc.selection.style("background-color");
-      ctx.brushed.globalAlpha = 1 - __.alphaOnBrushed;
-      ctx.brushed.fillRect(0, 0, w() + 2, h() + 2);
-      ctx.brushed.globalAlpha = __.alpha;
-    }
-    return this;
-  };
-
-  d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");
-
-  function flipAxisAndUpdatePCP(dimension) {
-    var g = pc.svg.selectAll(".dimension");
-
-    pc.flip(dimension);
-
-    d3.select(this.parentElement)
-      .transition()
-        .duration(1100)
-        .call(axis.scale(yscale[dimension]));
-
-    pc.render();
-  }
-
-  function rotateLabels() {
-    var delta = d3.event.deltaY;
-    delta = delta < 0 ? -5 : delta;
-    delta = delta > 0 ? 5 : delta;
-
-    __.dimensionTitleRotation += delta;
-    pc.svg.selectAll("text.label")
-      .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");
-    d3.event.preventDefault();
-  }
-
-  function dimensionLabels(d) {
-    return d in __.dimensionTitles ? __.dimensionTitles[d] : d;  // dimension display names
-  }
-
-  pc.createAxes = function() {
-    if (g) pc.removeAxes();
-
-    // Add a group element for each dimension.
-    g = pc.svg.selectAll(".dimension")
-        .data(__.dimensions, function(d) { return d; })
-      .enter().append("svg:g")
-        .attr("class", "dimension")
-        .attr("transform", function(d) { return "translate(" + xscale(d) + ")"; });
-
-    // Add an axis and title.
-    g.append("svg:g")
-        .attr("class", "axis")
-        .attr("transform", "translate(0,0)")
-        .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
-      .append("svg:text")
-        .attr({
-          "text-anchor": "middle",
-          "y": 0,
-          "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
-          "x": 0,
-          "class": "label"
-        })
-        .text(dimensionLabels)
-        .on("dblclick", flipAxisAndUpdatePCP)
-        .on("wheel", rotateLabels);
-
-    if (__.nullValueSeparator=="top") {
-      pc.svg.append("line")
-        .attr("x1", 0)
-        .attr("y1", 1+__.nullValueSeparatorPadding.top)
-        .attr("x2", w())
-        .attr("y2", 1+__.nullValueSeparatorPadding.top)
-        .attr("stroke-width", 1)
-        .attr("stroke", "#777")
-        .attr("fill", "none")
-        .attr("shape-rendering", "crispEdges");
-    } else if (__.nullValueSeparator=="bottom") {
-      pc.svg.append("line")
-        .attr("x1", 0)
-        .attr("y1", h()+1-__.nullValueSeparatorPadding.bottom)
-        .attr("x2", w())
-        .attr("y2", h()+1-__.nullValueSeparatorPadding.bottom)
-        .attr("stroke-width", 1)
-        .attr("stroke", "#777")
-        .attr("fill", "none")
-        .attr("shape-rendering", "crispEdges");
-    }
-
-    flags.axes= true;
-    return this;
-  };
-
-  pc.removeAxes = function() {
-    g.remove();
-    return this;
-  };
-
-  pc.updateAxes = function() {
-    var g_data = pc.svg.selectAll(".dimension").data(__.dimensions);
-
-    // Enter
-    g_data.enter().append("svg:g")
-        .attr("class", "dimension")
-        .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
-        .style("opacity", 0)
-      .append("svg:g")
-        .attr("class", "axis")
-        .attr("transform", "translate(0,0)")
-        .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
-      .append("svg:text")
-        .attr({
-          "text-anchor": "middle",
-          "y": 0,
-          "transform": "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")",
-          "x": 0,
-          "class": "label"
-        })
-        .text(dimensionLabels)
-        .on("dblclick", flipAxisAndUpdatePCP)
-        .on("wheel", rotateLabels);
-
-    // Update
-    g_data.attr("opacity", 0);
-    g_data.select(".axis")
-      .transition()
-        .duration(1100)
-        .each(function(d) {
-          d3.select(this).call(axis.scale(yscale[d]));
-        });
-    g_data.select(".label")
-      .transition()
-        .duration(1100)
-        .text(dimensionLabels)
-        .attr("transform", "translate(0,-5) rotate(" + __.dimensionTitleRotation + ")");
-
-    // Exit
-    g_data.exit().remove();
-
-    g = pc.svg.selectAll(".dimension");
-    g.transition().duration(1100)
-      .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
-      .style("opacity", 1);
-
-    pc.svg.selectAll(".axis")
-      .transition()
-        .duration(1100)
-        .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); });
-
-    if (flags.brushable) pc.brushable();
-    if (flags.reorderable) pc.reorderable();
-    if (pc.brushMode() !== "None") {
-      var mode = pc.brushMode();
-      pc.brushMode("None");
-      pc.brushMode(mode);
-    }
-    return this;
-  };
-
-  // Jason Davies, http://bl.ocks.org/1341281
-  pc.reorderable = function() {
-    if (!g) pc.createAxes();
-
-    g.style("cursor", "move")
-      .call(d3.behavior.drag()
-        .on("dragstart", function(d) {
-          dragging[d] = this.__origin__ = xscale(d);
-        })
-        .on("drag", function(d) {
-          dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
-          __.dimensions.sort(function(a, b) { return position(a) - position(b); });
-          xscale.domain(__.dimensions);
-          pc.render();
-          g.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
-        })
-        .on("dragend", function(d) {
-          // Let's see if the order has changed and send out an event if so.
-          var i = 0,
-              j = __.dimensions.indexOf(d),
-              elem = this,
-              parent = this.parentElement;
-
-          while((elem = elem.previousElementSibling) != null) ++i;
-          if (i !== j) {
-            events.axesreorder.call(pc, __.dimensions);
-            // We now also want to reorder the actual dom elements that represent
-            // the axes. That is, the g.dimension elements. If we don't do this,
-            // we get a weird and confusing transition when updateAxes is called.
-            // This is due to the fact that, initially the nth g.dimension element
-            // represents the nth axis. However, after a manual reordering,
-            // without reordering the dom elements, the nth dom elements no longer
-            // necessarily represents the nth axis.
-            //
-            // i is the original index of the dom element
-            // j is the new index of the dom element
-            if (i > j) { // Element moved left
-              parent.insertBefore(this, parent.children[j - 1]);
-            } else {     // Element moved right
-              if ((j + 1) < parent.children.length) {
-                parent.insertBefore(this, parent.children[j + 1]);
-              } else {
-                parent.appendChild(this);
-              }
-            }
-          }
-
-          delete this.__origin__;
-          delete dragging[d];
-          d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
-          pc.render();
-        }));
-    flags.reorderable = true;
-    return this;
-  };
-
-  // Reorder dimensions, such that the highest value (visually) is on the left and
-  // the lowest on the right. Visual values are determined by the data values in
-  // the given row.
-  pc.reorder = function(rowdata) {
-    var dims = __.dimensions.slice(0);
-    __.dimensions.sort(function(a, b) {
-      var pixelDifference = yscale[a](rowdata[a]) - yscale[b](rowdata[b]);
-
-      // Array.sort is not necessarily stable, this means that if pixelDifference is zero
-      // the ordering of dimensions might change unexpectedly. This is solved by sorting on
-      // variable name in that case.
-      if (pixelDifference === 0) {
-        return a.localeCompare(b);
-      } // else
-      return pixelDifference;
-    });
-
-    // NOTE: this is relatively cheap given that:
-    // number of dimensions < number of data items
-    // Thus we check equality of order to prevent rerendering when this is the case.
-    var reordered = false;
-    dims.some(function(val, index) {
-      reordered = val !== __.dimensions[index];
-      return reordered;
-    });
-
-    if (reordered) {
-      xscale.domain(__.dimensions);
-      var highlighted = __.highlighted.slice(0);
-      pc.unhighlight();
-
-      g.transition()
-        .duration(1500)
-        .attr("transform", function(d) {
-          return "translate(" + xscale(d) + ")";
-        });
-      pc.render();
-
-      // pc.highlight() does not check whether highlighted is length zero, so we do that here.
-      if (highlighted.length !== 0) {
-        pc.highlight(highlighted);
-      }
-    }
-  }
-
-  // pairs of adjacent dimensions
-  pc.adjacent_pairs = function(arr) {
-    var ret = [];
-    for (var i = 0; i < arr.length-1; i++) {
-      ret.push([arr[i],arr[i+1]]);
-    };
-    return ret;
-  };
-
-  var brush = {
-    modes: {
-      "None": {
-        install: function(pc) {},            // Nothing to be done.
-        uninstall: function(pc) {},          // Nothing to be done.
-        selected: function() { return []; }, // Nothing to return
-        brushState: function() { return {}; }
-      }
-    },
-    mode: "None",
-    predicate: "AND",
-    currentMode: function() {
-      return this.modes[this.mode];
-    }
-  };
-
-  // This function can be used for 'live' updates of brushes. That is, during the
-  // specification of a brush, this method can be called to update the view.
-  //
-  // @param newSelection - The new set of data items that is currently contained
-  //                       by the brushes
-  function brushUpdated(newSelection) {
-    __.brushed = newSelection;
-    events.brush.call(pc,__.brushed);
-    pc.renderBrushed();
-  }
-
-  function brushPredicate(predicate) {
-    if (!arguments.length) { return brush.predicate; }
-
-    predicate = String(predicate).toUpperCase();
-    if (predicate !== "AND" && predicate !== "OR") {
-      throw "Invalid predicate " + predicate;
-    }
-
-    brush.predicate = predicate;
-    __.brushed = brush.currentMode().selected();
-    pc.renderBrushed();
-    return pc;
-  }
-
-  pc.brushModes = function() {
-    return Object.getOwnPropertyNames(brush.modes);
-  };
-
-  pc.brushMode = function(mode) {
-    if (arguments.length === 0) {
-      return brush.mode;
-    }
-
-    if (pc.brushModes().indexOf(mode) === -1) {
-      throw "pc.brushmode: Unsupported brush mode: " + mode;
-    }
-
-    // Make sure that we don't trigger unnecessary events by checking if the mode
-    // actually changes.
-    if (mode !== brush.mode) {
-      // When changing brush modes, the first thing we need to do is clearing any
-      // brushes from the current mode, if any.
-      if (brush.mode !== "None") {
-        pc.brushReset();
-      }
-
-      // Next, we need to 'uninstall' the current brushMode.
-      brush.modes[brush.mode].uninstall(pc);
-      // Finally, we can install the requested one.
-      brush.mode = mode;
-      brush.modes[brush.mode].install();
-      if (mode === "None") {
-        delete pc.brushPredicate;
-      } else {
-        pc.brushPredicate = brushPredicate;
-      }
-    }
-
-    return pc;
-  };
-
-  // brush mode: 1D-Axes
-
-  (function() {
-    var brushes = {};
-
-    function is_brushed(p) {
-      return !brushes[p].empty();
-    }
-
-    // data within extents
-    function selected() {
-      var actives = __.dimensions.filter(is_brushed),
-          extents = actives.map(function(p) { return brushes[p].extent(); });
-
-      // We don't want to return the full data set when there are no axes brushed.
-      // Actually, when there are no axes brushed, by definition, no items are
-      // selected. So, let's avoid the filtering and just return false.
-      //if (actives.length === 0) return false;
-
-      // Resolves broken examples for now. They expect to get the full dataset back from empty brushes
-      if (actives.length === 0) return __.data;
-
-      // test if within range
-      var within = {
-        "date": function(d,p,dimension) {
-    if (typeof yscale[p].rangePoints === "function") { // if it is ordinal
-            return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
-          } else {
-            return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
-          }
-        },
-        "number": function(d,p,dimension) {
-          if (typeof yscale[p].rangePoints === "function") { // if it is ordinal
-            return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
-          } else {
-            return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
-          }
-        },
-        "string": function(d,p,dimension) {
-          return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
-        }
-      };
-
-      return __.data
-        .filter(function(d) {
-          switch(brush.predicate) {
-          case "AND":
-            return actives.every(function(p, dimension) {
-              return within[__.types[p]](d,p,dimension);
-            });
-          case "OR":
-            return actives.some(function(p, dimension) {
-              return within[__.types[p]](d,p,dimension);
-            });
-          default:
-            throw "Unknown brush predicate " + __.brushPredicate;
-          }
-        });
-    };
-
-    function brushExtents(extents) {
-      if(typeof(extents) === 'undefined')
-      {
-        var extents = {};
-        __.dimensions.forEach(function(d) {
-          var brush = brushes[d];
-          if (brush !== undefined && !brush.empty()) {
-            var extent = brush.extent();
-            extent.sort(d3.ascending);
-            extents[d] = extent;
-          }
-        });
-        return extents;
-      }
-      else
-      {
-        //first get all the brush selections
-        var brushSelections = {};
-        g.selectAll('.brush')
-          .each(function(d) {
-            brushSelections[d] = d3.select(this);
-
-        });
-
-        // loop over each dimension and update appropriately (if it was passed in through extents)
-        __.dimensions.forEach(function(d) {
-          if (extents[d] === undefined){
-            return;
-          }
-
-          var brush = brushes[d];
-          if (brush !== undefined) {
-            //update the extent
-            brush.extent(extents[d]);
-
-            //redraw the brush
-            brush(brushSelections[d]);
-
-            //fire some events
-            brush.event(brushSelections[d]);
-          }
-        });
-
-        //redraw the chart
-        pc.renderBrushed();
-      }
-    }
-    function brushFor(axis) {
-      var brush = d3.svg.brush();
-
-      brush
-        .y(yscale[axis])
-        .on("brushstart", function() {
-          if(d3.event.sourceEvent !== null) {
-            d3.event.sourceEvent.stopPropagation();
-          }
-        })
-        .on("brush", function() {
-          brushUpdated(selected());
-        })
-        .on("brushend", function() {
-          events.brushend.call(pc, __.brushed);
-        });
-
-      brushes[axis] = brush;
-      return brush;
-    };
-    function brushReset(dimension) {
-      __.brushed = false;
-      if (g) {
-        g.selectAll('.brush')
-          .each(function(d) {
-            d3.select(this).call(
-              brushes[d].clear()
-            );
-          });
-        pc.renderBrushed();
-      }
-      return this;
-    };
-
-    function install() {
-      if (!g) pc.createAxes();
-
-      // Add and store a brush for each axis.
-      g.append("svg:g")
-        .attr("class", "brush")
-        .each(function(d) {
-          d3.select(this).call(brushFor(d));
-        })
-        .selectAll("rect")
-          .style("visibility", null)
-          .attr("x", -15)
-          .attr("width", 30);
-
-      pc.brushExtents = brushExtents;
-      pc.brushReset = brushReset;
-      return pc;
-    };
-
-    brush.modes["1D-axes"] = {
-      install: install,
-      uninstall: function() {
-        g.selectAll(".brush").remove();
-        brushes = {};
-        delete pc.brushExtents;
-        delete pc.brushReset;
-      },
-      selected: selected,
-      brushState: brushExtents
-    }
-  })();
-  // brush mode: 2D-strums
-  // bl.ocks.org/syntagmatic/5441022
-
-  (function() {
-    var strums = {},
-        strumRect;
-
-    function drawStrum(strum, activePoint) {
-      var svg = pc.selection.select("svg").select("g#strums"),
-          id = strum.dims.i,
-          points = [strum.p1, strum.p2],
-          line = svg.selectAll("line#strum-" + id).data([strum]),
-          circles = svg.selectAll("circle#strum-" + id).data(points),
-          drag = d3.behavior.drag();
-
-      line.enter()
-        .append("line")
-        .attr("id", "strum-" + id)
-        .attr("class", "strum");
-
-      line
-        .attr("x1", function(d) { return d.p1[0]; })
-        .attr("y1", function(d) { return d.p1[1]; })
-        .attr("x2", function(d) { return d.p2[0]; })
-        .attr("y2", function(d) { return d.p2[1]; })
-        .attr("stroke", "black")
-        .attr("stroke-width", 2);
-
-      drag
-        .on("drag", function(d, i) {
-          var ev = d3.event;
-          i = i + 1;
-          strum["p" + i][0] = Math.min(Math.max(strum.minX + 1, ev.x), strum.maxX);
-          strum["p" + i][1] = Math.min(Math.max(strum.minY, ev.y), strum.maxY);
-          drawStrum(strum, i - 1);
-        })
-        .on("dragend", onDragEnd());
-
-      circles.enter()
-        .append("circle")
-        .attr("id", "strum-" + id)
-        .attr("class", "strum");
-
-      circles
-        .attr("cx", function(d) { return d[0]; })
-        .attr("cy", function(d) { return d[1]; })
-        .attr("r", 5)
-        .style("opacity", function(d, i) {
-          return (activePoint !== undefined && i === activePoint) ? 0.8 : 0;
-        })
-        .on("mouseover", function() {
-          d3.select(this).style("opacity", 0.8);
-        })
-        .on("mouseout", function() {
-          d3.select(this).style("opacity", 0);
-        })
-        .call(drag);
-    }
-
-    function dimensionsForPoint(p) {
-      var dims = { i: -1, left: undefined, right: undefined };
-      __.dimensions.some(function(dim, i) {
-        if (xscale(dim) < p[0]) {
-          var next = __.dimensions[i + 1];
-          dims.i = i;
-          dims.left = dim;
-          dims.right = next;
-          return false;
-        }
-        return true;
-      });
-
-      if (dims.left === undefined) {
-        // Event on the left side of the first axis.
-        dims.i = 0;
-        dims.left = __.dimensions[0];
-        dims.right = __.dimensions[1];
-      } else if (dims.right === undefined) {
-        // Event on the right side of the last axis
-        dims.i = __.dimensions.length - 1;
-        dims.right = dims.left;
-        dims.left = __.dimensions[__.dimensions.length - 2];
-      }
-
-      return dims;
-    }
-
-    function onDragStart() {
-      // First we need to determine between which two axes the sturm was started.
-      // This will determine the freedom of movement, because a strum can
-      // logically only happen between two axes, so no movement outside these axes
-      // should be allowed.
-      return function() {
-        var p = d3.mouse(strumRect[0][0]),
-            dims,
-            strum;
-
-        p[0] = p[0] - __.margin.left;
-        p[1] = p[1] - __.margin.top;
-
-        dims = dimensionsForPoint(p),
-        strum = {
-          p1: p,
-          dims: dims,
-          minX: xscale(dims.left),
-          maxX: xscale(dims.right),
-          minY: 0,
-          maxY: h()
-        };
-
-        strums[dims.i] = strum;
-        strums.active = dims.i;
-
-        // Make sure that the point is within the bounds
-        strum.p1[0] = Math.min(Math.max(strum.minX, p[0]), strum.maxX);
-        strum.p2 = strum.p1.slice();
-      };
-    }
-
-    function onDrag() {
-      return function() {
-        var ev = d3.event,
-            strum = strums[strums.active];
-
-        // Make sure that the point is within the bounds
-        strum.p2[0] = Math.min(Math.max(strum.minX + 1, ev.x - __.margin.left), strum.maxX);
-        strum.p2[1] = Math.min(Math.max(strum.minY, ev.y - __.margin.top), strum.maxY);
-        drawStrum(strum, 1);
-      };
-    }
-
-    function containmentTest(strum, width) {
-      var p1 = [strum.p1[0] - strum.minX, strum.p1[1] - strum.minX],
-          p2 = [strum.p2[0] - strum.minX, strum.p2[1] - strum.minX],
-          m1 = 1 - width / p1[0],
-          b1 = p1[1] * (1 - m1),
-          m2 = 1 - width / p2[0],
-          b2 = p2[1] * (1 - m2);
-
-      // test if point falls between lines
-      return function(p) {
-        var x = p[0],
-            y = p[1],
-            y1 = m1 * x + b1,
-            y2 = m2 * x + b2;
-
-        if (y > Math.min(y1, y2) && y < Math.max(y1, y2)) {
-          return true;
-        }
-
-        return false;
-      };
-    }
-
-    function selected() {
-      var ids = Object.getOwnPropertyNames(strums),
-          brushed = __.data;
-
-      // Get the ids of the currently active strums.
-      ids = ids.filter(function(d) {
-        return !isNaN(d);
-      });
-
-      function crossesStrum(d, id) {
-        var strum = strums[id],
-            test = containmentTest(strum, strums.width(id)),
-            d1 = strum.dims.left,
-            d2 = strum.dims.right,
-            y1 = yscale[d1],
-            y2 = yscale[d2],
-            point = [y1(d[d1]) - strum.minX, y2(d[d2]) - strum.minX];
-        return test(point);
-      }
-
-      if (ids.length === 0) { return brushed; }
-
-      return brushed.filter(function(d) {
-        switch(brush.predicate) {
-        case "AND":
-          return ids.every(function(id) { return crossesStrum(d, id); });
-        case "OR":
-          return ids.some(function(id) { return crossesStrum(d, id); });
-        default:
-          throw "Unknown brush predicate " + __.brushPredicate;
-        }
-      });
-    }
-
-    function removeStrum() {
-      var strum = strums[strums.active],
-          svg = pc.selection.select("svg").select("g#strums");
-
-      delete strums[strums.active];
-      strums.active = undefined;
-      svg.selectAll("line#strum-" + strum.dims.i).remove();
-      svg.selectAll("circle#strum-" + strum.dims.i).remove();
-    }
-
-    function onDragEnd() {
-      return function() {
-        var brushed = __.data,
-            strum = strums[strums.active];
-
-        // Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
-        // considered a drag without move. So we have to deal with that case
-        if (strum && strum.p1[0] === strum.p2[0] && strum.p1[1] === strum.p2[1]) {
-          removeStrum(strums);
-        }
-
-        brushed = selected(strums);
-        strums.active = undefined;
-        __.brushed = brushed;
-        pc.renderBrushed();
-        events.brushend.call(pc, __.brushed);
-      };
-    }
-
-    function brushReset(strums) {
-      return function() {
-        var ids = Object.getOwnPropertyNames(strums).filter(function(d) {
-          return !isNaN(d);
-        });
-
-        ids.forEach(function(d) {
-          strums.active = d;
-          removeStrum(strums);
-        });
-        onDragEnd(strums)();
-      };
-    }
-
-    function install() {
-      var drag = d3.behavior.drag();
-
-      // Map of current strums. Strums are stored per segment of the PC. A segment,
-      // being the area between two axes. The left most area is indexed at 0.
-      strums.active = undefined;
-      // Returns the width of the PC segment where currently a strum is being
-      // placed. NOTE: even though they are evenly spaced in our current
-      // implementation, we keep for when non-even spaced segments are supported as
-      // well.
-      strums.width = function(id) {
-        var strum = strums[id];
-
-        if (strum === undefined) {
-          return undefined;
-        }
-
-        return strum.maxX - strum.minX;
-      };
-
-      pc.on("axesreorder.strums", function() {
-        var ids = Object.getOwnPropertyNames(strums).filter(function(d) {
-          return !isNaN(d);
-        });
-
-        // Checks if the first dimension is directly left of the second dimension.
-        function consecutive(first, second) {
-          var length = __.dimensions.length;
-          return __.dimensions.some(function(d, i) {
-            return (d === first)
-              ? i + i < length && __.dimensions[i + 1] === second
-              : false;
-          });
-        }
-
-        if (ids.length > 0) { // We have some strums, which might need to be removed.
-          ids.forEach(function(d) {
-            var dims = strums[d].dims;
-            strums.active = d;
-            // If the two dimensions of the current strum are not next to each other
-            // any more, than we'll need to remove the strum. Otherwise we keep it.
-            if (!consecutive(dims.left, dims.right)) {
-              removeStrum(strums);
-            }
-          });
-          onDragEnd(strums)();
-        }
-      });
-
-      // Add a new svg group in which we draw the strums.
-      pc.selection.select("svg").append("g")
-        .attr("id", "strums")
-        .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
-
-      // Install the required brushReset function
-      pc.brushReset = brushReset(strums);
-
-      drag
-        .on("dragstart", onDragStart(strums))
-        .on("drag", onDrag(strums))
-        .on("dragend", onDragEnd(strums));
-
-      // NOTE: The styling needs to be done here and not in the css. This is because
-      //       for 1D brushing, the canvas layers should not listen to
-      //       pointer-events.
-      strumRect = pc.selection.select("svg").insert("rect", "g#strums")
-        .attr("id", "strum-events")
-        .attr("x", __.margin.left)
-        .attr("y", __.margin.top)
-        .attr("width", w())
-        .attr("height", h() + 2)
-        .style("opacity", 0)
-        .call(drag);
-    }
-
-    brush.modes["2D-strums"] = {
-      install: install,
-      uninstall: function() {
-        pc.selection.select("svg").select("g#strums").remove();
-        pc.selection.select("svg").select("rect#strum-events").remove();
-        pc.on("axesreorder.strums", undefined);
-        delete pc.brushReset;
-
-        strumRect = undefined;
-      },
-      selected: selected,
-      brushState: function () { return strums; }
-    };
-
-  }());
-
-  // brush mode: 1D-Axes with multiple extents
-  // requires d3.svg.multibrush
-
-  (function() {
-    if (typeof d3.svg.multibrush !== 'function') {
-      return;
-    }
-    var brushes = {};
-
-    function is_brushed(p) {
-      return !brushes[p].empty();
-    }
-
-    // data within extents
-    function selected() {
-      var actives = __.dimensions.filter(is_brushed),
-          extents = actives.map(function(p) { return brushes[p].extent(); });
-
-      // We don't want to return the full data set when there are no axes brushed.
-      // Actually, when there are no axes brushed, by definition, no items are
-      // selected. So, let's avoid the filtering and just return false.
-      //if (actives.length === 0) return false;
-
-      // Resolves broken examples for now. They expect to get the full dataset back from empty brushes
-      if (actives.length === 0) return __.data;
-
-      // test if within range
-      var within = {
-        "date": function(d,p,dimension,b) {
-          if (typeof yscale[p].rangePoints === "function") { // if it is ordinal
-            return b[0] <= yscale[p](d[p]) && yscale[p](d[p]) <= b[1]
-          } else {
-              return b[0] <= d[p] && d[p] <= b[1]
-          }
-        },
-        "number": function(d,p,dimension,b) {
-          if (typeof yscale[p].rangePoints === "function") { // if it is ordinal
-            return b[0] <= yscale[p](d[p]) && yscale[p](d[p]) <= b[1]
-          } else {
-              return b[0] <= d[p] && d[p] <= b[1]
-          }
-        },
-        "string": function(d,p,dimension,b) {
-          return b[0] <= yscale[p](d[p]) && yscale[p](d[p]) <= b[1]
-        }
-      };
-
-      return __.data
-      .filter(function(d) {
-        switch(brush.predicate) {
-        case "AND":
-          return actives.every(function(p, dimension) {
-            return extents[dimension].some(function(b) {
-              return within[__.types[p]](d,p,dimension,b);
-            });
-          });
-        case "OR":
-          return actives.some(function(p, dimension) {
-            return extents[dimension].some(function(b) {
-                return within[__.types[p]](d,p,dimension,b);
-              });
-          });
-        default:
-          throw "Unknown brush predicate " + __.brushPredicate;
-        }
-      });
-    };
-
-    function brushExtents() {
-      var extents = {};
-      __.dimensions.forEach(function(d) {
-        var brush = brushes[d];
-        if (brush !== undefined && !brush.empty()) {
-          var extent = brush.extent();
-          extents[d] = extent;
-        }
-      });
-      return extents;
-    }
-
-    function brushFor(axis) {
-      var brush = d3.svg.multibrush();
-
-      brush
-        .y(yscale[axis])
-        .on("brushstart", function() {
-          if(d3.event.sourceEvent !== null) {
-            d3.event.sourceEvent.stopPropagation();
-          }
-        })
-        .on("brush", function() {
-          brushUpdated(selected());
-        })
-        .on("brushend", function() {
-        // d3.svg.multibrush clears extents just before calling 'brushend'
-        // so we have to update here again.
-        // This fixes issue #103 for now, but should be changed in d3.svg.multibrush
-        // to avoid unnecessary computation.
-        brushUpdated(selected());
-          events.brushend.call(pc, __.brushed);
-        })
-        .extentAdaption(function(selection) {
-          selection
-          .style("visibility", null)
-            .attr("x", -15)
-            .attr("width", 30);
-        })
-        .resizeAdaption(function(selection) {
-         selection
-           .selectAll("rect")
-           .attr("x", -15)
-           .attr("width", 30);
-        });
-
-      brushes[axis] = brush;
-      return brush;
-    }
-
-    function brushReset(dimension) {
-      __.brushed = false;
-      if (g) {
-        g.selectAll('.brush')
-          .each(function(d) {
-            d3.select(this).call(
-              brushes[d].clear()
-            );
-          });
-        pc.renderBrushed();
-      }
-      return this;
-    };
-
-    function install() {
-      if (!g) pc.createAxes();
-
-      // Add and store a brush for each axis.
-      g.append("svg:g")
-        .attr("class", "brush")
-        .each(function(d) {
-          d3.select(this).call(brushFor(d));
-        })
-        .selectAll("rect")
-          .style("visibility", null)
-          .attr("x", -15)
-          .attr("width", 30);
-
-      pc.brushExtents = brushExtents;
-      pc.brushReset = brushReset;
-      return pc;
-    }
-
-    brush.modes["1D-axes-multi"] = {
-      install: install,
-      uninstall: function() {
-        g.selectAll(".brush").remove();
-        brushes = {};
-        delete pc.brushExtents;
-        delete pc.brushReset;
-      },
-      selected: selected,
-      brushState: brushExtents
-    }
-  })();
-  // brush mode: angular
-  // code based on 2D.strums.js
-
-  (function() {
-    var arcs = {},
-        strumRect;
-
-    function drawStrum(arc, activePoint) {
-      var svg = pc.selection.select("svg").select("g#arcs"),
-          id = arc.dims.i,
-          points = [arc.p2, arc.p3],
-          line = svg.selectAll("line#arc-" + id).data([{p1:arc.p1,p2:arc.p2},{p1:arc.p1,p2:arc.p3}]),
-          circles = svg.selectAll("circle#arc-" + id).data(points),
-          drag = d3.behavior.drag(),
-          path = svg.selectAll("path#arc-" + id).data([arc]);
-
-      path.enter()
-        .append("path")
-        .attr("id", "arc-" + id)
-        .attr("class", "arc")
-        .style("fill", "orange")
-        .style("opacity", 0.5);
-
-      path
-        .attr("d", arc.arc)
-        .attr("transform", "translate(" + arc.p1[0] + "," + arc.p1[1] + ")");
-
-      line.enter()
-        .append("line")
-        .attr("id", "arc-" + id)
-        .attr("class", "arc");
-
-      line
-        .attr("x1", function(d) { return d.p1[0]; })
-        .attr("y1", function(d) { return d.p1[1]; })
-        .attr("x2", function(d) { return d.p2[0]; })
-        .attr("y2", function(d) { return d.p2[1]; })
-        .attr("stroke", "black")
-        .attr("stroke-width", 2);
-
-      drag
-        .on("drag", function(d, i) {
-          var ev = d3.event,
-            angle = 0;
-
-          i = i + 2;
-
-          arc["p" + i][0] = Math.min(Math.max(arc.minX + 1, ev.x), arc.maxX);
-          arc["p" + i][1] = Math.min(Math.max(arc.minY, ev.y), arc.maxY);
-
-          angle = i === 3 ? arcs.startAngle(id) : arcs.endAngle(id);
-
-          if ((arc.startAngle < Math.PI && arc.endAngle < Math.PI && angle < Math.PI) ||
-              (arc.startAngle >= Math.PI && arc.endAngle >= Math.PI && angle >= Math.PI)) {
-
-            if (i === 2) {
-              arc.endAngle = angle;
-              arc.arc.endAngle(angle);
-            } else if (i === 3) {
-              arc.startAngle = angle;
-              arc.arc.startAngle(angle);
-            }
-
-          }
-
-          drawStrum(arc, i - 2);
-        })
-        .on("dragend", onDragEnd());
-
-      circles.enter()
-        .append("circle")
-        .attr("id", "arc-" + id)
-        .attr("class", "arc");
-
-      circles
-        .attr("cx", function(d) { return d[0]; })
-        .attr("cy", function(d) { return d[1]; })
-        .attr("r", 5)
-        .style("opacity", function(d, i) {
-          return (activePoint !== undefined && i === activePoint) ? 0.8 : 0;
-        })
-        .on("mouseover", function() {
-          d3.select(this).style("opacity", 0.8);
-        })
-        .on("mouseout", function() {
-          d3.select(this).style("opacity", 0);
-        })
-        .call(drag);
-    }
-
-    function dimensionsForPoint(p) {
-      var dims = { i: -1, left: undefined, right: undefined };
-      __.dimensions.some(function(dim, i) {
-        if (xscale(dim) < p[0]) {
-          var next = __.dimensions[i + 1];
-          dims.i = i;
-          dims.left = dim;
-          dims.right = next;
-          return false;
-        }
-        return true;
-      });
-
-      if (dims.left === undefined) {
-        // Event on the left side of the first axis.
-        dims.i = 0;
-        dims.left = __.dimensions[0];
-        dims.right = __.dimensions[1];
-      } else if (dims.right === undefined) {
-        // Event on the right side of the last axis
-        dims.i = __.dimensions.length - 1;
-        dims.right = dims.left;
-        dims.left = __.dimensions[__.dimensions.length - 2];
-      }
-
-      return dims;
-    }
-
-    function onDragStart() {
-      // First we need to determine between which two axes the arc was started.
-      // This will determine the freedom of movement, because a arc can
-      // logically only happen between two axes, so no movement outside these axes
-      // should be allowed.
-      return function() {
-        var p = d3.mouse(strumRect[0][0]),
-            dims,
-            arc;
-
-        p[0] = p[0] - __.margin.left;
-        p[1] = p[1] - __.margin.top;
-
-        dims = dimensionsForPoint(p),
-        arc = {
-          p1: p,
-          dims: dims,
-          minX: xscale(dims.left),
-          maxX: xscale(dims.right),
-          minY: 0,
-          maxY: h(),
-          startAngle: undefined,
-          endAngle: undefined,
-          arc: d3.svg.arc().innerRadius(0)
-        };
-
-        arcs[dims.i] = arc;
-        arcs.active = dims.i;
-
-        // Make sure that the point is within the bounds
-        arc.p1[0] = Math.min(Math.max(arc.minX, p[0]), arc.maxX);
-        arc.p2 = arc.p1.slice();
-        arc.p3 = arc.p1.slice();
-      };
-    }
-
-    function onDrag() {
-      return function() {
-        var ev = d3.event,
-            arc = arcs[arcs.active];
-
-        // Make sure that the point is within the bounds
-        arc.p2[0] = Math.min(Math.max(arc.minX + 1, ev.x - __.margin.left), arc.maxX);
-        arc.p2[1] = Math.min(Math.max(arc.minY, ev.y - __.margin.top), arc.maxY);
-        arc.p3 = arc.p2.slice();
-  //      console.log(arcs.angle(arcs.active));
-  //      console.log(signedAngle(arcs.unsignedAngle(arcs.active)));
-        drawStrum(arc, 1);
-      };
-    }
-
-    // some helper functions
-    function hypothenuse(a, b) {
-      return Math.sqrt(a*a + b*b);
-    }
-
-    var rad = (function() {
-      var c = Math.PI / 180;
-      return function(angle) {
-        return angle * c;
-      };
-    })();
-
-    var deg = (function() {
-      var c = 180 / Math.PI;
-      return function(angle) {
-        return angle * c;
-      };
-    })();
-
-    // [0, 2*PI] -> [-PI/2, PI/2]
-    var signedAngle = function(angle) {
-      var ret = angle;
-      if (angle > Math.PI) {
-      ret = angle - 1.5 * Math.PI;
-      ret = angle - 1.5 * Math.PI;
-      } else {
-        ret = angle - 0.5 * Math.PI;
-        ret = angle - 0.5 * Math.PI;
-      }
-      return -ret;
-    }
-
-    /**
-     * angles are stored in radians from in [0, 2*PI], where 0 in 12 o'clock.
-     * However, one can only select lines from 0 to PI, so we compute the
-     * 'signed' angle, where 0 is the horizontal line (3 o'clock), and +/- PI/2
-     * are 12 and 6 o'clock respectively.
-     */
-    function containmentTest(arc) {
-      var startAngle = signedAngle(arc.startAngle);
-      var endAngle = signedAngle(arc.endAngle);
-
-      if (startAngle > endAngle) {
-        var tmp = startAngle;
-        startAngle = endAngle;
-        endAngle = tmp;
-      }
-
-      // test if segment angle is contained in angle interval
-      return function(a) {
-
-        if (a >= startAngle && a <= endAngle) {
-          return true;
-        }
-
-        return false;
-      };
-    }
-
-    function selected() {
-      var ids = Object.getOwnPropertyNames(arcs),
-          brushed = __.data;
-
-      // Get the ids of the currently active arcs.
-      ids = ids.filter(function(d) {
-        return !isNaN(d);
-      });
-
-      function crossesStrum(d, id) {
-        var arc = arcs[id],
-            test = containmentTest(arc),
-            d1 = arc.dims.left,
-            d2 = arc.dims.right,
-            y1 = yscale[d1],
-            y2 = yscale[d2],
-            a = arcs.width(id),
-            b = y1(d[d1]) - y2(d[d2]),
-            c = hypothenuse(a, b),
-            angle = Math.asin(b/c); // rad in [-PI/2, PI/2]
-        return test(angle);
-      }
-
-      if (ids.length === 0) { return brushed; }
-
-      return brushed.filter(function(d) {
-        switch(brush.predicate) {
-        case "AND":
-          return ids.every(function(id) { return crossesStrum(d, id); });
-        case "OR":
-          return ids.some(function(id) { return crossesStrum(d, id); });
-        default:
-          throw "Unknown brush predicate " + __.brushPredicate;
-        }
-      });
-    }
-
-    function removeStrum() {
-      var arc = arcs[arcs.active],
-          svg = pc.selection.select("svg").select("g#arcs");
-
-      delete arcs[arcs.active];
-      arcs.active = undefined;
-      svg.selectAll("line#arc-" + arc.dims.i).remove();
-      svg.selectAll("circle#arc-" + arc.dims.i).remove();
-      svg.selectAll("path#arc-" + arc.dims.i).remove();
-    }
-
-    function onDragEnd() {
-      return function() {
-        var brushed = __.data,
-            arc = arcs[arcs.active];
-
-        // Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
-        // considered a drag without move. So we have to deal with that case
-        if (arc && arc.p1[0] === arc.p2[0] && arc.p1[1] === arc.p2[1]) {
-          removeStrum(arcs);
-        }
-
-        if (arc) {
-          var angle = arcs.startAngle(arcs.active);
-
-          arc.startAngle = angle;
-            arc.endAngle = angle;
-            arc.arc
-              .outerRadius(arcs.length(arcs.active))
-              .startAngle(angle)
-              .endAngle(angle);
-        }
-
-
-        brushed = selected(arcs);
-        arcs.active = undefined;
-        __.brushed = brushed;
-        pc.renderBrushed();
-        events.brushend.call(pc, __.brushed);
-      };
-    }
-
-    function brushReset(arcs) {
-      return function() {
-        var ids = Object.getOwnPropertyNames(arcs).filter(function(d) {
-          return !isNaN(d);
-        });
-
-        ids.forEach(function(d) {
-          arcs.active = d;
-          removeStrum(arcs);
-        });
-        onDragEnd(arcs)();
-      };
-    }
-
-    function install() {
-      var drag = d3.behavior.drag();
-
-      // Map of current arcs. arcs are stored per segment of the PC. A segment,
-      // being the area between two axes. The left most area is indexed at 0.
-      arcs.active = undefined;
-      // Returns the width of the PC segment where currently a arc is being
-      // placed. NOTE: even though they are evenly spaced in our current
-      // implementation, we keep for when non-even spaced segments are supported as
-      // well.
-      arcs.width = function(id) {
-        var arc = arcs[id];
-
-        if (arc === undefined) {
-          return undefined;
-        }
-
-        return arc.maxX - arc.minX;
-      };
-
-      // returns angles in [-PI/2, PI/2]
-      angle = function(p1, p2) {
-          var a = p1[0] - p2[0],
-            b = p1[1] - p2[1],
-            c = hypothenuse(a, b);
-
-          return Math.asin(b/c);
-      }
-
-      // returns angles in [0, 2 * PI]
-      arcs.endAngle = function(id) {
-        var arc = arcs[id];
-        if (arc === undefined) {
-              return undefined;
-          }
-        var sAngle = angle(arc.p1, arc.p2),
-          uAngle = -sAngle + Math.PI / 2;
-
-        if (arc.p1[0] > arc.p2[0]) {
-          uAngle = 2 * Math.PI - uAngle;
-        }
-
-        return uAngle;
-      }
-
-      arcs.startAngle = function(id) {
-        var arc = arcs[id];
-        if (arc === undefined) {
-              return undefined;
-          }
-
-        var sAngle = angle(arc.p1, arc.p3),
-          uAngle = -sAngle + Math.PI / 2;
-
-        if (arc.p1[0] > arc.p3[0]) {
-          uAngle = 2 * Math.PI - uAngle;
-        }
-
-        return uAngle;
-      }
-
-      arcs.length = function(id) {
-        var arc = arcs[id];
-
-          if (arc === undefined) {
-            return undefined;
-          }
-
-          var a = arc.p1[0] - arc.p2[0],
-            b = arc.p1[1] - arc.p2[1],
-            c = hypothenuse(a, b);
-
-          return(c);
-      }
-
-      pc.on("axesreorder.arcs", function() {
-        var ids = Object.getOwnPropertyNames(arcs).filter(function(d) {
-          return !isNaN(d);
-        });
-
-        // Checks if the first dimension is directly left of the second dimension.
-        function consecutive(first, second) {
-          var length = __.dimensions.length;
-          return __.dimensions.some(function(d, i) {
-            return (d === first)
-              ? i + i < length && __.dimensions[i + 1] === second
-              : false;
-          });
-        }
-
-        if (ids.length > 0) { // We have some arcs, which might need to be removed.
-          ids.forEach(function(d) {
-            var dims = arcs[d].dims;
-            arcs.active = d;
-            // If the two dimensions of the current arc are not next to each other
-            // any more, than we'll need to remove the arc. Otherwise we keep it.
-            if (!consecutive(dims.left, dims.right)) {
-              removeStrum(arcs);
-            }
-          });
-          onDragEnd(arcs)();
-        }
-      });
-
-      // Add a new svg group in which we draw the arcs.
-      pc.selection.select("svg").append("g")
-        .attr("id", "arcs")
-        .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
-
-      // Install the required brushReset function
-      pc.brushReset = brushReset(arcs);
-
-      drag
-        .on("dragstart", onDragStart(arcs))
-        .on("drag", onDrag(arcs))
-        .on("dragend", onDragEnd(arcs));
-
-      // NOTE: The styling needs to be done here and not in the css. This is because
-      //       for 1D brushing, the canvas layers should not listen to
-      //       pointer-events.
-      strumRect = pc.selection.select("svg").insert("rect", "g#arcs")
-        .attr("id", "arc-events")
-        .attr("x", __.margin.left)
-        .attr("y", __.margin.top)
-        .attr("width", w())
-        .attr("height", h() + 2)
-        .style("opacity", 0)
-        .call(drag);
-    }
-
-    brush.modes["angular"] = {
-      install: install,
-      uninstall: function() {
-        pc.selection.select("svg").select("g#arcs").remove();
-        pc.selection.select("svg").select("rect#arc-events").remove();
-        pc.on("axesreorder.arcs", undefined);
-        delete pc.brushReset;
-
-        strumRect = undefined;
-      },
-      selected: selected,
-      brushState: function () { return arcs; }
-    };
-
-  }());
-
-  pc.interactive = function() {
-    flags.interactive = true;
-    return this;
-  };
-
-  // expose a few objects
-  pc.xscale = xscale;
-  pc.yscale = yscale;
-  pc.ctx = ctx;
-  pc.canvas = canvas;
-  pc.g = function() { return g; };
-
-  // rescale for height, width and margins
-  // TODO currently assumes chart is brushable, and destroys old brushes
-  pc.resize = function() {
-    // selection size
-    pc.selection.select("svg")
-      .attr("width", __.width)
-      .attr("height", __.height)
-    pc.svg.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
-
-    // FIXME: the current brush state should pass through
-    if (flags.brushable) pc.brushReset();
-
-    // scales
-    pc.autoscale();
-
-    // axes, destroys old brushes.
-    if (g) pc.createAxes();
-    if (flags.brushable) pc.brushable();
-    if (flags.reorderable) pc.reorderable();
-
-    events.resize.call(this, {width: __.width, height: __.height, margin: __.margin});
-    return this;
-  };
-
-  // highlight an array of data
-  pc.highlight = function(data) {
-    if (arguments.length === 0) {
-      return __.highlighted;
-    }
-
-    __.highlighted = data;
-    pc.clear("highlight");
-    d3.selectAll([canvas.foreground, canvas.brushed]).classed("faded", true);
-    data.forEach(path_highlight);
-    events.highlight.call(this, data);
-    return this;
-  };
-
-  // clear highlighting
-  pc.unhighlight = function() {
-    __.highlighted = [];
-    pc.clear("highlight");
-    d3.selectAll([canvas.foreground, canvas.brushed]).classed("faded", false);
-    return this;
-  };
-
-  // calculate 2d intersection of line a->b with line c->d
-  // points are objects with x and y properties
-  pc.intersection =  function(a, b, c, d) {
-    return {
-      x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
-      y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
-    };
-  };
-
-  function position(d) {
-    var v = dragging[d];
-    return v == null ? xscale(d) : v;
-  }
-  pc.version = "0.7.0";
-    // this descriptive text should live with other introspective methods
-    pc.toString = function() { return "Parallel Coordinates: " + __.dimensions.length + " dimensions (" + d3.keys(__.data[0]).length + " total) , " + __.data.length + " rows"; };
-
-    return pc;
-  };
-
-  d3.renderQueue = (function(func) {
-    var _queue = [],                  // data to be rendered
-        _rate = 10,                   // number of calls per frame
-        _clear = function() {},       // clearing function
-        _i = 0;                       // current iteration
-
-    var rq = function(data) {
-      if (data) rq.data(data);
-      rq.invalidate();
-      _clear();
-      rq.render();
-    };
-
-    rq.render = function() {
-      _i = 0;
-      var valid = true;
-      rq.invalidate = function() { valid = false; };
-
-      function doFrame() {
-        if (!valid) return true;
-        if (_i > _queue.length) return true;
-
-        // Typical d3 behavior is to pass a data item *and* its index. As the
-        // render queue splits the original data set, we'll have to be slightly
-        // more carefull about passing the correct index with the data item.
-        var end = Math.min(_i + _rate, _queue.length);
-        for (var i = _i; i < end; i++) {
-          func(_queue[i], i);
-        }
-        _i += _rate;
-      }
-
-      d3.timer(doFrame);
-    };
-
-    rq.data = function(data) {
-      rq.invalidate();
-      _queue = data.slice(0);
-      return rq;
-    };
-
-    rq.rate = function(value) {
-      if (!arguments.length) return _rate;
-      _rate = value;
-      return rq;
-    };
-
-    rq.remaining = function() {
-      return _queue.length - _i;
-    };
-
-    // clear the canvas
-    rq.clear = function(func) {
-      if (!arguments.length) {
-        _clear();
-        return rq;
-      }
-      _clear = func;
-      return rq;
-    };
-
-    rq.invalidate = function() {};
-
-    return rq;
-  });
diff --git a/panoramix/assets/vendor/parallel_coordinates/divgrid.js b/panoramix/assets/vendor/parallel_coordinates/divgrid.js
deleted file mode 100644
index e4086e8bae5b..000000000000
--- a/panoramix/assets/vendor/parallel_coordinates/divgrid.js
+++ /dev/null
@@ -1,59 +0,0 @@
-// from http://bl.ocks.org/3687826
-module.exports = function(config) {
-  var columns = [];
-
-  var dg = function(selection) {
-    if (columns.length == 0) columns = d3.keys(selection.data()[0][0]);
-
-    // header
-    selection.selectAll(".header")
-        .data([true])
-      .enter().append("div")
-        .attr("class", "header")
-
-    var header = selection.select(".header")
-      .selectAll(".cell")
-      .data(columns);
-
-    header.enter().append("div")
-      .attr("class", function(d,i) { return "col-" + i; })
-      .classed("cell", true)
-
-    selection.selectAll(".header .cell")
-      .text(function(d) { return d; });
-
-    header.exit().remove();
-
-    // rows
-    var rows = selection.selectAll(".row")
-        .data(function(d) { return d; })
-
-    rows.enter().append("div")
-        .attr("class", "row")
-
-    rows.exit().remove();
-
-    var cells = selection.selectAll(".row").selectAll(".cell")
-        .data(function(d) { return columns.map(function(col){return d[col];}) })
-
-    // cells
-    cells.enter().append("div")
-      .attr("class", function(d,i) { return "col-" + i; })
-      .classed("cell", true)
-
-    cells.exit().remove();
-
-    selection.selectAll(".cell")
-      .text(function(d) { return d; });
-
-    return dg;
-  };
-
-  dg.columns = function(_) {
-    if (!arguments.length) return columns;
-    columns = _;
-    return this;
-  };
-
-  return dg;
-};
diff --git a/panoramix/assets/vendor/pygments.css b/panoramix/assets/vendor/pygments.css
deleted file mode 100644
index ef95359ffa2c..000000000000
--- a/panoramix/assets/vendor/pygments.css
+++ /dev/null
@@ -1,62 +0,0 @@
-.codehilite .hll { background-color: #ffffcc }
-.codehilite  { background: #f8f8f8; }
-.codehilite .c { color: #408080; font-style: italic } /* Comment */
-.codehilite .err { border: 1px solid #FF0000 } /* Error */
-.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
-.codehilite .o { color: #666666 } /* Operator */
-.codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
-.codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
-.codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
-.codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
-.codehilite .gd { color: #A00000 } /* Generic.Deleted */
-.codehilite .ge { font-style: italic } /* Generic.Emph */
-.codehilite .gr { color: #FF0000 } /* Generic.Error */
-.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.codehilite .gi { color: #00A000 } /* Generic.Inserted */
-.codehilite .go { color: #808080 } /* Generic.Output */
-.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.codehilite .gs { font-weight: bold } /* Generic.Strong */
-.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.codehilite .gt { color: #0040D0 } /* Generic.Traceback */
-.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
-.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
-.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
-.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
-.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
-.codehilite .kt { color: #B00040 } /* Keyword.Type */
-.codehilite .m { color: #666666 } /* Literal.Number */
-.codehilite .s { color: #BA2121 } /* Literal.String */
-.codehilite .na { color: #7D9029 } /* Name.Attribute */
-.codehilite .nb { color: #008000 } /* Name.Builtin */
-.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
-.codehilite .no { color: #880000 } /* Name.Constant */
-.codehilite .nd { color: #AA22FF } /* Name.Decorator */
-.codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
-.codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
-.codehilite .nf { color: #0000FF } /* Name.Function */
-.codehilite .nl { color: #A0A000 } /* Name.Label */
-.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.codehilite .nv { color: #19177C } /* Name.Variable */
-.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
-.codehilite .mf { color: #666666 } /* Literal.Number.Float */
-.codehilite .mh { color: #666666 } /* Literal.Number.Hex */
-.codehilite .mi { color: #666666 } /* Literal.Number.Integer */
-.codehilite .mo { color: #666666 } /* Literal.Number.Oct */
-.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
-.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
-.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
-.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
-.codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
-.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
-.codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
-.codehilite .sx { color: #008000 } /* Literal.String.Other */
-.codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
-.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
-.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
-.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
-.codehilite .vc { color: #19177C } /* Name.Variable.Class */
-.codehilite .vg { color: #19177C } /* Name.Variable.Global */
-.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
-.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/panoramix/assets/vendor/select2.sortable.js b/panoramix/assets/vendor/select2.sortable.js
deleted file mode 100644
index 18fac5104a68..000000000000
--- a/panoramix/assets/vendor/select2.sortable.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * jQuery Select2 Sortable
- * - enable select2 to be sortable via normal select element
- *
- * author      : Vafour
- * modified    : Kevin Provance (kprovance)
- * inspired by : jQuery Chosen Sortable (https://github.com/mrhenry/jquery-chosen-sortable)
- * License     : GPL
- */
-
-(function ($) {
-    $.fn.extend({
-        select2SortableOrder: function () {
-            var $this = this.filter('[multiple]');
-
-            $this.each(function () {
-                var $select = $(this);
-
-                // skip elements not select2-ed
-                if (typeof ($select.data('select2')) !== 'object') {
-                    return false;
-                }
-
-                var $select2 = $select.siblings('.select2-container');
-                var sorted;
-
-                // Opt group names
-                var optArr = [];
-
-                $select.find('optgroup').each(function(idx, val) {
-                    optArr.push (val);
-                });
-
-                $select.find('option').each(function(idx, val) {
-                    var groupName = $(this).parent('optgroup').prop('label');
-                    var optVal = this;
-
-                    if (groupName === undefined) {
-                        if (this.value !== '' && !this.selected) {
-                            optArr.push (optVal);
-                        }
-                    }
-                });
-
-                sorted = $($select2.find('.select2-choices li[class!="select2-search-field"]').map(function () {
-                    if (!this) {
-                        return undefined;
-                    }
-
-                    var id = $(this).data('select2Data').id;
-
-                    return $select.find('option[value="' + id + '"]')[0];
-                }));
-
-                 sorted.push.apply(sorted, optArr);
-
-                $select.children().remove();
-                $select.append(sorted);
-              });
-
-            return $this;
-        },
-
-        select2Sortable: function () {
-            var args = Array.prototype.slice.call(arguments, 0);
-            var $this = this.filter('[multiple]'),
-                    validMethods = ['destroy'];
-
-            if (args.length === 0 || typeof (args[0]) === 'object') {
-                var defaultOptions = {
-                    bindOrder: 'formSubmit', // or sortableStop
-                    sortableOptions: {
-                        placeholder: 'ui-state-highlight',
-                        items: 'li:not(.select2-search-field)',
-                        tolerance: 'pointer'
-                    }
-                };
-
-                var options = $.extend(defaultOptions, args[0]);
-
-                // Init select2 only if not already initialized to prevent select2 configuration loss
-                if (typeof ($this.data('select2')) !== 'object') {
-                    $this.select2();
-                }
-
-                $this.each(function () {
-                    var $select = $(this)
-                    var $select2choices = $select.siblings('.select2-container').find('.select2-choices');
-
-                    // Init jQuery UI Sortable
-                    $select2choices.sortable(options.sortableOptions);
-
-                    switch (options.bindOrder) {
-                        case 'sortableStop':
-                            // apply options ordering in sortstop event
-                            $select2choices.on("sortstop.select2sortable", function (event, ui) {
-                                $select.select2SortableOrder();
-                            });
-
-                            $select.on('change', function (e) {
-                                $(this).select2SortableOrder();
-                            });
-                        break;
-
-                        default:
-                            // apply options ordering in form submit
-                            $select.closest('form').unbind('submit.select2sortable').on('submit.select2sortable', function () {
-                                $select.select2SortableOrder();
-                            });
-                        break;
-                    }
-                });
-            }
-            else if (typeof (args[0] === 'string')) {
-                if ($.inArray(args[0], validMethods) == -1) {
-                    throw "Unknown method: " + args[0];
-                }
-
-                if (args[0] === 'destroy') {
-                    $this.select2SortableDestroy();
-                }
-            }
-
-            return $this;
-        },
-
-        select2SortableDestroy: function () {
-            var $this = this.filter('[multiple]');
-            $this.each(function () {
-                var $select = $(this)
-                var $select2choices = $select.parent().find('.select2-choices');
-
-                // unbind form submit event
-                $select.closest('form').unbind('submit.select2sortable');
-
-                // unbind sortstop event
-                $select2choices.unbind("sortstop.select2sortable");
-
-                // destroy select2Sortable
-                $select2choices.sortable('destroy');
-            });
-
-            return $this;
-        }
-    });
-}(jQuery));
diff --git a/panoramix/assets/visualizations/big_number.css b/panoramix/assets/visualizations/big_number.css
deleted file mode 100644
index 872a5715a23f..000000000000
--- a/panoramix/assets/visualizations/big_number.css
+++ /dev/null
@@ -1,26 +0,0 @@
-.big_number g.axis text {
-  font-size: 10px;
-  font-weight: normal;
-  color: gray;
-  fill: gray;
-  text-anchor: middle;
-  alignment-baseline: middle;
-  font-weight: none;
-}
-
-.big_number text.big {
-  stroke: black;
-  text-anchor: middle;
-  fill: black;
-}
-
-.big_number g.tick line {
-  stroke-width: 1px;
-  stroke: grey;
-}
-
-.big_number .domain {
-  fill: none;
-  stroke: black;
-  stroke-width: 1;
-}
diff --git a/panoramix/assets/visualizations/big_number.js b/panoramix/assets/visualizations/big_number.js
deleted file mode 100644
index f1b0461ec631..000000000000
--- a/panoramix/assets/visualizations/big_number.js
+++ /dev/null
@@ -1,162 +0,0 @@
-// JS
-var d3 = window.d3 || require('d3');
-
-// CSS
-require('./big_number.css');
-
-var px = require('../javascripts/modules/panoramix.js');
-
-function bigNumberVis(slice) {
-  var div = d3.select(slice.selector);
-
-  function render() {
-    d3.json(slice.jsonEndpoint(), function (error, payload) {
-      //Define the percentage bounds that define color from red to green
-      if (error !== null) {
-        slice.error(error.responseText);
-        return '';
-      }
-      var fd = payload.form_data;
-      var json = payload.data;
-      var color_range = [-1, 1];
-
-      var f = d3.format(fd.y_axis_format);
-      var fp = d3.format('+.1%');
-      var width = slice.width();
-      var height = slice.height();
-      var svg = div.append('svg');
-      svg.attr("width", width);
-      svg.attr("height", height);
-      var data = json.data;
-      var compare_suffix = ' ' + json.compare_suffix;
-      var v_compare = null;
-      var v = data[data.length - 1][1];
-      if (json.compare_lag > 0) {
-        var pos = data.length - (json.compare_lag + 1);
-        if (pos >= 0) {
-          v_compare = (v / data[pos][1]) - 1;
-        }
-      }
-      var date_ext = d3.extent(data, function (d) {
-        return d[0];
-      });
-      var value_ext = d3.extent(data, function (d) {
-        return d[1];
-      });
-
-      var margin = 20;
-      var scale_x = d3.time.scale.utc().domain(date_ext).range([margin, width - margin]);
-      var scale_y = d3.scale.linear().domain(value_ext).range([height - (margin), margin]);
-      var colorRange = [d3.hsl(0, 1, 0.3), d3.hsl(120, 1, 0.3)];
-      var scale_color = d3.scale
-        .linear().domain(color_range)
-        .interpolate(d3.interpolateHsl)
-        .range(colorRange).clamp(true);
-      var line = d3.svg.line()
-        .x(function (d) {
-          return scale_x(d[0]);
-        })
-        .y(function (d) {
-          return scale_y(d[1]);
-        })
-        .interpolate("basis");
-
-      //Drawing trend line
-      var g = svg.append('g');
-
-      g.append('path')
-        .attr('d', function (d) {
-          return line(data);
-        })
-        .attr('stroke-width', 5)
-        .attr('opacity', 0.5)
-        .attr('fill', "none")
-        .attr('stroke-linecap', "round")
-        .attr('stroke', "grey");
-
-      g = svg.append('g')
-        .attr('class', 'digits')
-        .attr('opacity', 1);
-
-      var y = height / 2;
-      if (v_compare !== null) {
-        y = (height / 8) * 3;
-      }
-
-      //Printing big number
-      g.append('text')
-        .attr('x', width / 2)
-        .attr('y', y)
-        .attr('class', 'big')
-        .attr('alignment-baseline', 'middle')
-        .attr('id', 'bigNumber')
-        .style('font-weight', 'bold')
-        .style('cursor', 'pointer')
-        .text(f(v))
-        .style('font-size', d3.min([height, width]) / 3.5)
-        .attr('fill', 'white');
-
-      var c = scale_color(v_compare);
-
-      //Printing compare %
-      if (v_compare !== null) {
-        g.append('text')
-          .attr('x', width / 2)
-          .attr('y', (height / 16) * 12)
-          .text(fp(v_compare) + compare_suffix)
-          .style('font-size', d3.min([height, width]) / 8)
-          .style('text-anchor', 'middle')
-          .attr('fill', c)
-          .attr('stroke', c);
-      }
-
-      var g_axis = svg.append('g').attr('class', 'axis').attr('opacity', 0);
-      g = g_axis.append('g');
-      var x_axis = d3.svg.axis()
-        .scale(scale_x)
-        .orient('bottom')
-        .ticks(4)
-        .tickFormat(px.formatDate);
-      g.call(x_axis);
-      g.attr('transform', 'translate(0,' + (height - margin) + ')');
-
-      g = g_axis.append('g').attr('transform', 'translate(' + (width - margin) + ',0)');
-      var y_axis = d3.svg.axis()
-        .scale(scale_y)
-        .orient('left')
-        .tickFormat(d3.format(fd.y_axis_format))
-        .tickValues(value_ext);
-      g.call(y_axis);
-      g.selectAll('text')
-        .style('text-anchor', 'end')
-        .attr('y', '-7')
-        .attr('x', '-4');
-
-      g.selectAll("text")
-        .style('font-size', '10px');
-
-      div.on('mouseover', function (d) {
-          var div = d3.select(this);
-          div.select('path').transition().duration(500).attr('opacity', 1)
-            .style('stroke-width', '2px');
-          div.select('g.digits').transition().duration(500).attr('opacity', 0.1);
-          div.select('g.axis').transition().duration(500).attr('opacity', 1);
-        })
-        .on('mouseout', function (d) {
-          var div = d3.select(this);
-          div.select('path').transition().duration(500).attr('opacity', 0.5)
-            .style('stroke-width', '5px');
-          div.select('g.digits').transition().duration(500).attr('opacity', 1);
-          div.select('g.axis').transition().duration(500).attr('opacity', 0);
-        });
-      slice.done(payload);
-    });
-  }
-
-  return {
-    render: render,
-    resize: render
-  };
-}
-
-module.exports = bigNumberVis;
diff --git a/panoramix/assets/visualizations/directed_force.css b/panoramix/assets/visualizations/directed_force.css
deleted file mode 100644
index 170eccba1945..000000000000
--- a/panoramix/assets/visualizations/directed_force.css
+++ /dev/null
@@ -1,19 +0,0 @@
-.directed_force path.link {
-  fill: none;
-  stroke: #000;
-  stroke-width: 1.5px;
-}
-
-.directed_force circle {
-  fill: #ccc;
-  stroke: #000;
-  stroke-width: 1.5px;
-  stroke-opacity: 1;
-  opacity: 0.75;
-}
-
-.directed_force text {
-  fill: #000;
-  font: 10px sans-serif;
-  pointer-events: none;
-}
diff --git a/panoramix/assets/visualizations/directed_force.js b/panoramix/assets/visualizations/directed_force.js
deleted file mode 100644
index a0252067e636..000000000000
--- a/panoramix/assets/visualizations/directed_force.js
+++ /dev/null
@@ -1,175 +0,0 @@
-// JS
-var d3 = window.d3 || require('d3');
-
-// CSS
-require('./directed_force.css');
-
-/* Modified from http://bl.ocks.org/d3noob/5141278 */
-function directedForceVis(slice) {
-  var div = d3.select(slice.selector);
-  var link_length = slice.data.form_data.link_length || 200;
-  var charge = slice.data.form_data.charge || -500;
-
-  var render = function () {
-    var width = slice.width();
-    var height = slice.height() - 25;
-    d3.json(slice.jsonEndpoint(), function (error, json) {
-
-      if (error !== null) {
-        slice.error(error.responseText);
-        return '';
-      }
-      var links = json.data;
-      var nodes = {};
-      // Compute the distinct nodes from the links.
-      links.forEach(function (link) {
-        link.source = nodes[link.source] || (nodes[link.source] = {
-          name: link.source
-        });
-        link.target = nodes[link.target] || (nodes[link.target] = {
-          name: link.target
-        });
-        link.value = Number(link.value);
-
-        var target_name = link.target.name;
-        var source_name = link.source.name;
-
-        if (nodes[target_name].total === undefined) {
-          nodes[target_name].total = link.value;
-        }
-        if (nodes[source_name].total === undefined) {
-          nodes[source_name].total = 0;
-        }
-        if (nodes[target_name].max === undefined) {
-          nodes[target_name].max = 0;
-        }
-        if (link.value > nodes[target_name].max) {
-          nodes[target_name].max = link.value;
-        }
-        if (nodes[target_name].min === undefined) {
-          nodes[target_name].min = 0;
-        }
-        if (link.value > nodes[target_name].min) {
-          nodes[target_name].min = link.value;
-        }
-
-        nodes[target_name].total += link.value;
-      });
-
-      var force = d3.layout.force()
-        .nodes(d3.values(nodes))
-        .links(links)
-        .size([width, height])
-        .linkDistance(link_length)
-        .charge(charge)
-        .on("tick", tick)
-        .start();
-
-      var svg = div.append("svg")
-        .attr("width", width)
-        .attr("height", height);
-
-      // build the arrow.
-      svg.append("svg:defs").selectAll("marker")
-        .data(["end"]) // Different link/path types can be defined here
-        .enter().append("svg:marker") // This section adds in the arrows
-        .attr("id", String)
-        .attr("viewBox", "0 -5 10 10")
-        .attr("refX", 15)
-        .attr("refY", -1.5)
-        .attr("markerWidth", 6)
-        .attr("markerHeight", 6)
-        .attr("orient", "auto")
-        .append("svg:path")
-        .attr("d", "M0,-5L10,0L0,5");
-
-      var edgeScale = d3.scale.linear()
-        .range([0.1, 0.5]);
-      // add the links and the arrows
-      var path = svg.append("svg:g").selectAll("path")
-        .data(force.links())
-        .enter().append("svg:path")
-        .attr("class", "link")
-        .style("opacity", function (d) {
-          return edgeScale(d.value / d.target.max);
-        })
-        .attr("marker-end", "url(#end)");
-
-      // define the nodes
-      var node = svg.selectAll(".node")
-        .data(force.nodes())
-        .enter().append("g")
-        .attr("class", "node")
-        .on("mouseenter", function (d) {
-          d3.select(this)
-            .select("circle")
-            .transition()
-            .style('stroke-width', 5);
-
-          d3.select(this)
-            .select("text")
-            .transition()
-            .style('font-size', 25);
-        })
-        .on("mouseleave", function (d) {
-          d3.select(this)
-            .select("circle")
-            .transition()
-            .style('stroke-width', 1.5);
-          d3.select(this)
-            .select("text")
-            .transition()
-            .style('font-size', 12);
-        })
-        .call(force.drag);
-
-      // add the nodes
-      var ext = d3.extent(d3.values(nodes), function (d) {
-        return Math.sqrt(d.total);
-      });
-      var circleScale = d3.scale.linear()
-        .domain(ext)
-        .range([3, 30]);
-
-      node.append("circle")
-        .attr("r", function (d) {
-          return circleScale(Math.sqrt(d.total));
-        });
-
-      // add the text
-      node.append("text")
-        .attr("x", 6)
-        .attr("dy", ".35em")
-        .text(function (d) {
-          return d.name;
-        });
-
-      // add the curvy lines
-      function tick() {
-        path.attr("d", function (d) {
-          var dx = d.target.x - d.source.x,
-            dy = d.target.y - d.source.y,
-            dr = Math.sqrt(dx * dx + dy * dy);
-          return "M" +
-            d.source.x + "," +
-            d.source.y + "A" +
-            dr + "," + dr + " 0 0,1 " +
-            d.target.x + "," +
-            d.target.y;
-        });
-
-        node.attr("transform", function (d) {
-          return "translate(" + d.x + "," + d.y + ")";
-        });
-      }
-
-      slice.done(json);
-    });
-  };
-  return {
-    render: render,
-    resize: render
-  };
-}
-
-module.exports = directedForceVis;
diff --git a/panoramix/assets/visualizations/filter_box.css b/panoramix/assets/visualizations/filter_box.css
deleted file mode 100644
index 15697836166d..000000000000
--- a/panoramix/assets/visualizations/filter_box.css
+++ /dev/null
@@ -1,8 +0,0 @@
-.select2-highlighted > .filter_box {
-    background-color: transparent;
-    border: 1px dashed black;
-}
-
-.dashboard .filter_box .slice_container > div {
-  padding-top: 0;
-}
diff --git a/panoramix/assets/visualizations/filter_box.js b/panoramix/assets/visualizations/filter_box.js
deleted file mode 100644
index f4714b4637bb..000000000000
--- a/panoramix/assets/visualizations/filter_box.js
+++ /dev/null
@@ -1,82 +0,0 @@
-// JS
-var $ = window.$ = require('jquery');
-var jQuery = window.jQuery = $;
-var d3 = window.d3 || require('d3');
-
-// CSS
-require('./filter_box.css');
-require('../javascripts/panoramix-select2.js');
-
-function filterBox(slice) {
-  var filtersObj = {};
-  var d3token = d3.select(slice.selector);
-
-  var fltChanged = function () {
-    var val = $(this).val();
-    var vals = [];
-    if (val !== '') {
-      vals = val.split(',');
-    }
-    slice.setFilter($(this).attr('name'), vals);
-  };
-
-  var refresh = function () {
-    d3token.selectAll("*").remove();
-    var container = d3token
-      .append('div')
-      .classed('padded', true);
-
-    $.getJSON(slice.jsonEndpoint(), function (payload) {
-        var maxes = {};
-
-        for (var filter in payload.data) {
-          var data = payload.data[filter];
-          maxes[filter] = d3.max(data, function (d) {
-            return d.metric;
-          });
-          var id = 'fltbox__' + filter;
-
-          var div = container.append('div');
-
-          div.append("label").text(filter);
-
-          div.append('div')
-            .attr('name', filter)
-            .classed('form-control', true)
-            .attr('multiple', '')
-            .attr('id', id);
-
-          filtersObj[filter] = $('#' + id).select2({
-              placeholder: "Select [" + filter + ']',
-              containment: 'parent',
-              dropdownAutoWidth: true,
-              data: data,
-              multiple: true,
-              formatResult: select2Formatter
-            })
-            .on('change', fltChanged);
-        }
-        slice.done();
-
-        function select2Formatter(result, container /*, query, escapeMarkup*/) {
-          var perc = Math.round((result.metric / maxes[result.filter]) * 100);
-          var style = 'padding: 2px 5px;';
-          style += "background-image: ";
-          style += "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
-
-          $(container).attr('style', 'padding: 0px; background: white;');
-          $(container).addClass('filter_box');
-          return '
' + result.text + '
'; - } - }) - .fail(function (xhr) { - slice.error(xhr.responseText); - }); - }; - return { - render: refresh, - resize: refresh - }; -} - -module.exports = filterBox; diff --git a/panoramix/assets/visualizations/heatmap.css b/panoramix/assets/visualizations/heatmap.css deleted file mode 100644 index bce124821230..000000000000 --- a/panoramix/assets/visualizations/heatmap.css +++ /dev/null @@ -1,79 +0,0 @@ -.heatmap .axis text { - font: 10px sans-serif; -} - -.heatmap .axis path, -.heatmap .axis line { - fill: none; - stroke: #000; - shape-rendering: crispEdges; -} - -.heatmap svg { -} - -.heatmap canvas, .heatmap img { - image-rendering: optimizeSpeed; /* Older versions of FF */ - image-rendering: -moz-crisp-edges; /* FF 6.0+ */ - image-rendering: -webkit-optimize-contrast; /* Safari */ - image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */ - image-rendering: pixelated; /* Awesome future-browsers */ - -ms-interpolation-mode: nearest-neighbor; /* IE */ -} - -/* from d3-tip */ -.d3-tip { - line-height: 1; - font-weight: bold; - padding: 12px; - background: rgba(0, 0, 0, 0.8); - color: #fff; - border-radius: 2px; - pointer-events: none; -} - -/* Creates a small triangle extender for the tooltip */ -.d3-tip:after { - box-sizing: border-box; - display: inline; - font-size: 10px; - width: 100%; - line-height: 1; - color: rgba(0, 0, 0, 0.8); - position: absolute; - pointer-events: none; -} - -/* Northward tooltips */ -.d3-tip.n:after { - content: "\25BC"; - margin: -1px 0 0 0; - top: 100%; - left: 0; - text-align: center; -} - -/* Eastward tooltips */ -.d3-tip.e:after { - content: "\25C0"; - margin: -4px 0 0 0; - top: 50%; - left: -8px; -} - -/* Southward tooltips */ -.d3-tip.s:after { - content: "\25B2"; - margin: 0 0 1px 0; - top: -8px; - left: 0; - text-align: center; -} - -/* Westward tooltips */ -.d3-tip.w:after { - content: "\25B6"; - margin: -4px 0 0 -1px; - top: 50%; - left: 100%; -} diff --git a/panoramix/assets/visualizations/heatmap.js b/panoramix/assets/visualizations/heatmap.js deleted file mode 100644 index 1112ae3807cd..000000000000 --- a/panoramix/assets/visualizations/heatmap.js +++ /dev/null @@ -1,209 +0,0 @@ -// JS -var $ = window.$ || require('jquery'); -var px = window.px || require('../javascripts/modules/panoramix.js'); -var d3 = require('d3'); - -d3.tip = require('d3-tip'); //using window.d3 doesn't capture events properly bc of multiple instances - -// CSS -require('./heatmap.css'); - -// Inspired from http://bl.ocks.org/mbostock/3074470 -// https://jsfiddle.net/cyril123/h0reyumq/ -function heatmapVis(slice) { - var margins = { - t: 10, - r: 10, - b: 50, - l: 60 - }; - - function refresh() { - var width = slice.width(); - var height = slice.height(); - var hmWidth = width - (margins.l + margins.r); - var hmHeight = height - (margins.b + margins.t); - var fp = d3.format('.3p'); - d3.json(slice.jsonEndpoint(), function (error, payload) { - var matrix = {}; - if (error) { - slice.error(error.responseText); - return ''; - } - var fd = payload.form_data; - var data = payload.data; - - function ordScale(k, rangeBands, reverse) { - if (reverse === undefined) { - reverse = false; - } - var domain = {}; - $.each(data, function (i, d) { - domain[d[k]] = true; - }); - domain = Object.keys(domain).sort(function (a, b) { - return b - a; - }); - if (reverse) { - domain.reverse(); - } - if (rangeBands === undefined) { - return d3.scale.ordinal().domain(domain).range(d3.range(domain.length)); - } else { - return d3.scale.ordinal().domain(domain).rangeBands(rangeBands); - } - } - var xScale = ordScale('x'); - var yScale = ordScale('y', undefined, true); - var xRbScale = ordScale('x', [0, hmWidth]); - var yRbScale = ordScale('y', [hmHeight, 0]); - var X = 0, - Y = 1; - var heatmapDim = [xRbScale.domain().length, yRbScale.domain().length]; - - var color = px.color.colorScalerFactory(fd.linear_color_scheme); - - var scale = [ - d3.scale.linear() - .domain([0, heatmapDim[X]]) - .range([0, hmWidth]), - d3.scale.linear() - .domain([0, heatmapDim[Y]]) - .range([0, hmHeight]) - ]; - - var container = d3.select(slice.selector) - .style("left", "0px") - .style("position", "relative") - .style("top", "0px"); - - var canvas = container.append("canvas") - .attr("width", heatmapDim[X]) - .attr("height", heatmapDim[Y]) - .style("width", hmWidth + "px") - .style("height", hmHeight + "px") - .style("image-rendering", fd.canvas_image_rendering) - .style("left", margins.l + "px") - .style("top", margins.t + "px") - .style("position", "absolute"); - - var svg = container.append("svg") - .attr("width", width) - .attr("height", height) - .style("left", "0px") - .style("top", "0px") - .style("position", "absolute"); - - var rect = svg.append('g') - .attr("transform", "translate(" + margins.l + "," + margins.t + ")") - .append('rect') - .style('fill-opacity', 0) - .attr('stroke', 'black') - .attr("width", hmWidth) - .attr("height", hmHeight); - - var tip = d3.tip() - .attr('class', 'd3-tip') - .offset(function () { - var k = d3.mouse(this); - var x = k[0] - (hmWidth / 2); - return [k[1] - 20, x]; - }) - .html(function (d) { - var k = d3.mouse(this); - var m = Math.floor(scale[0].invert(k[0])); - var n = Math.floor(scale[1].invert(k[1])); - if (m in matrix && n in matrix[m]) { - var obj = matrix[m][n]; - var s = ""; - s += "
" + fd.all_columns_x + ": " + obj.x + "
"; - s += "
" + fd.all_columns_y + ": " + obj.y + "
"; - s += "
" + fd.metric + ": " + obj.v + "
"; - s += "
%: " + fp(obj.perc) + "
"; - return s; - } - }); - - rect.call(tip); - - var xAxis = d3.svg.axis() - .scale(xRbScale) - .tickValues(xRbScale.domain().filter( - function (d, i) { - return !(i % (parseInt(fd.xscale_interval, 10))); - })) - .orient("bottom"); - var yAxis = d3.svg.axis() - .scale(yRbScale) - .tickValues(yRbScale.domain().filter( - function (d, i) { - return !(i % (parseInt(fd.yscale_interval, 10))); - })) - .orient("left"); - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(" + margins.l + "," + (margins.t + hmHeight) + ")") - .call(xAxis) - .selectAll("text") - .style("text-anchor", "end") - .attr("transform", "rotate(-45)") - .style("font-weight", "bold"); - - svg.append("g") - .attr("class", "y axis") - .attr("transform", "translate(" + margins.l + ", 0)") - .call(yAxis); - - rect.on('mousemove', tip.show); - rect.on('mouseout', tip.hide); - - var context = canvas.node().getContext("2d"); - context.imageSmoothingEnabled = false; - createImageObj(); - - // Compute the pixel colors; scaled by CSS. - function createImageObj() { - var imageObj = new Image(); - var image = context.createImageData(heatmapDim[0], heatmapDim[1]); - var pixs = {}; - $.each(data, function (i, d) { - var c = d3.rgb(color(d.perc)); - var x = xScale(d.x); - var y = yScale(d.y); - pixs[x + (y * xScale.domain().length)] = c; - if (matrix[x] === undefined) { - matrix[x] = {}; - } - if (matrix[x][y] === undefined) { - matrix[x][y] = d; - } - }); - - var p = -1; - for (var i = 0; i < heatmapDim[0] * heatmapDim[1]; i++) { - var c = pixs[i]; - var alpha = 255; - if (c === undefined) { - c = d3.rgb('#F00'); - alpha = 0; - } - image.data[++p] = c.r; - image.data[++p] = c.g; - image.data[++p] = c.b; - image.data[++p] = alpha; - } - context.putImageData(image, 0, 0); - imageObj.src = canvas.node().toDataURL(); - } - slice.done(); - - }); - } - return { - render: refresh, - resize: refresh - }; -} - -module.exports = heatmapVis; diff --git a/panoramix/assets/visualizations/iframe.js b/panoramix/assets/visualizations/iframe.js deleted file mode 100644 index 0f9fddc8ba2f..000000000000 --- a/panoramix/assets/visualizations/iframe.js +++ /dev/null @@ -1,25 +0,0 @@ -var $ = window.$ || require('jquery'); - -function iframeWidget(slice) { - - function refresh() { - $('#code').attr('rows', '15'); - $.getJSON(slice.jsonEndpoint(), function (payload) { - slice.container.html(''); - var iframe = slice.container.find('iframe'); - iframe.css('height', slice.height()); - iframe.attr('src', payload.form_data.url); - slice.done(); - }) - .fail(function (xhr) { - slice.error(xhr.responseText); - }); - } - - return { - render: refresh, - resize: refresh - }; -} - -module.exports = iframeWidget; diff --git a/panoramix/assets/visualizations/markup.js b/panoramix/assets/visualizations/markup.js deleted file mode 100644 index f202c92dd92b..000000000000 --- a/panoramix/assets/visualizations/markup.js +++ /dev/null @@ -1,23 +0,0 @@ -var $ = window.$ || require('jquery'); - -function markupWidget(slice) { - - function refresh() { - $('#code').attr('rows', '15'); - - $.getJSON(slice.jsonEndpoint(), function (payload) { - slice.container.html(payload.data.html); - slice.done(); - }) - .fail(function (xhr) { - slice.error(xhr.responseText); - }); - } - - return { - render: refresh, - resize: refresh - }; -} - -module.exports = markupWidget; diff --git a/panoramix/assets/visualizations/nvd3_vis.css b/panoramix/assets/visualizations/nvd3_vis.css deleted file mode 100644 index 80c452b4d6f7..000000000000 --- a/panoramix/assets/visualizations/nvd3_vis.css +++ /dev/null @@ -1,8 +0,0 @@ -g.dashed path { - stroke-dasharray: 5, 5; -} - -.nvtooltip tr.highlight td { - font-weight: bold; - font-size: 15px !important; -} diff --git a/panoramix/assets/visualizations/nvd3_vis.js b/panoramix/assets/visualizations/nvd3_vis.js deleted file mode 100644 index b9a15fa317d1..000000000000 --- a/panoramix/assets/visualizations/nvd3_vis.js +++ /dev/null @@ -1,208 +0,0 @@ -// JS -var $ = window.$ || require('jquery'); -var d3 = window.d3 || require('d3'); -var px = window.px || require('../javascripts/modules/panoramix.js'); -var nv = require('nvd3'); - -// CSS -require('../node_modules/nvd3/build/nv.d3.min.css'); -require('./nvd3_vis.css'); - -function nvd3Vis(slice) { - var chart; - - var render = function () { - $.getJSON(slice.jsonEndpoint(), function (payload) { - var fd = payload.form_data; - var viz_type = fd.viz_type; - - var f = d3.format('.3s'); - var colorKey = 'key'; - - nv.addGraph(function () { - switch (viz_type) { - case 'line': - if (fd.show_brush) { - chart = nv.models.lineWithFocusChart(); - chart.lines2.xScale(d3.time.scale.utc()); - chart.x2Axis - .showMaxMin(fd.x_axis_showminmax) - .staggerLabels(true); - } else { - chart = nv.models.lineChart(); - } - // To alter the tooltip header - // chart.interactiveLayer.tooltip.headerFormatter(function(){return '';}); - chart.xScale(d3.time.scale.utc()); - chart.interpolate(fd.line_interpolation); - chart.xAxis - .showMaxMin(fd.x_axis_showminmax) - .staggerLabels(true); - break; - - case 'bar': - chart = nv.models.multiBarChart() - .showControls(true) - .groupSpacing(0.1); - - chart.xAxis - .showMaxMin(false) - .staggerLabels(true); - - chart.stacked(fd.bar_stacked); - break; - - case 'dist_bar': - chart = nv.models.multiBarChart() - .showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode. - .reduceXTicks(false) - .rotateLabels(45) - .groupSpacing(0.1); //Distance between each group of bars. - - chart.xAxis - .showMaxMin(false); - - chart.stacked(fd.bar_stacked); - break; - - case 'pie': - chart = nv.models.pieChart(); - colorKey = 'x'; - chart.valueFormat(f); - if (fd.donut) { - chart.donut(true); - chart.labelsOutside(true); - } - chart.labelsOutside(true); - chart.cornerRadius(true); - break; - - case 'column': - chart = nv.models.multiBarChart() - .reduceXTicks(false) - .rotateLabels(45); - break; - - case 'compare': - chart = nv.models.cumulativeLineChart(); - chart.xScale(d3.time.scale.utc()); - chart.xAxis - .showMaxMin(false) - .staggerLabels(true); - break; - - case 'bubble': - var row = function (col1, col2) { - return "" + col1 + "" + col2 + ""; - }; - chart = nv.models.scatterChart(); - chart.showDistX(true); - chart.showDistY(true); - chart.tooltip.contentGenerator(function (obj) { - var p = obj.point; - var s = ""; - s += ''; - s += row(fd.x, f(p.x)); - s += row(fd.y, f(p.y)); - s += row(fd.size, f(p.size)); - s += "
' + p[fd.entity] + ' (' + p.group + ')
"; - return s; - }); - chart.pointRange([5, fd.max_bubble_size * fd.max_bubble_size]); - break; - - case 'area': - chart = nv.models.stackedAreaChart(); - chart.style(fd.stacked_style); - chart.xScale(d3.time.scale.utc()); - chart.xAxis - .showMaxMin(false) - .staggerLabels(true); - break; - - default: - throw new Error("Unrecognized visualization for nvd3" + viz_type); - } - - if ("showLegend" in chart && typeof fd.show_legend !== 'undefined') { - chart.showLegend(fd.show_legend); - } - - var height = slice.height(); - height -= 15; // accounting for the staggered xAxis - - if (chart.hasOwnProperty("x2Axis")) { - height += 30; - } - chart.height(height); - slice.container.css('height', height + 'px'); - - if ((viz_type === "line" || viz_type === "area") && fd.rich_tooltip) { - chart.useInteractiveGuideline(true); - } - if (fd.y_axis_zero) { - chart.forceY([0, 1]); - } else if (fd.y_log_scale) { - chart.yScale(d3.scale.log()); - } - if (fd.x_log_scale) { - chart.xScale(d3.scale.log()); - } - if (viz_type === 'bubble') { - chart.xAxis.tickFormat(d3.format('.3s')); - } else if (fd.x_axis_format === 'smart_date') { - chart.xAxis.tickFormat(px.formatDate); - } else if (fd.x_axis_format !== undefined) { - chart.xAxis.tickFormat(px.timeFormatFactory(fd.x_axis_format)); - } - if (chart.yAxis !== undefined) { - chart.yAxis.tickFormat(d3.format('.3s')); - } - - if (fd.contribution || fd.num_period_compare || viz_type === 'compare') { - chart.yAxis.tickFormat(d3.format('.3p')); - if (chart.y2Axis !== undefined) { - chart.y2Axis.tickFormat(d3.format('.3p')); - } - } else if (fd.y_axis_format) { - chart.yAxis.tickFormat(d3.format(fd.y_axis_format)); - - if (chart.y2Axis !== undefined) { - chart.y2Axis.tickFormat(d3.format(fd.y_axis_format)); - } - } - - chart.color(function (d, i) { - return px.color.category21(d[colorKey]); - }); - - d3.select(slice.selector).html(''); - d3.select(slice.selector).append("svg") - .datum(payload.data) - .transition().duration(500) - .attr('height', height) - .call(chart); - - return chart; - }); - - slice.done(payload); - }) - .fail(function (xhr) { - slice.error(xhr.responseText); - }); - }; - - var update = function () { - if (chart && chart.update) { - chart.update(); - } - }; - - return { - render: render, - resize: update - }; -} - -module.exports = nvd3Vis; diff --git a/panoramix/assets/visualizations/parallel_coordinates.js b/panoramix/assets/visualizations/parallel_coordinates.js deleted file mode 100644 index 271989b9f1bc..000000000000 --- a/panoramix/assets/visualizations/parallel_coordinates.js +++ /dev/null @@ -1,92 +0,0 @@ -// JS -var $ = window.$ || require('jquery'); -var d3 = window.d3 || require('d3'); -d3.parcoords = require('../vendor/parallel_coordinates/d3.parcoords.js'); -d3.divgrid = require('../vendor/parallel_coordinates/divgrid.js'); - -// CSS -require('../vendor/parallel_coordinates/d3.parcoords.css'); - -function parallelCoordVis(slice) { - - function refresh() { - $('#code').attr('rows', '15'); - $.getJSON(slice.jsonEndpoint(), function (payload) { - var data = payload.data; - var fd = payload.form_data; - var ext = d3.extent(data, function (d) { - return d[fd.secondary_metric]; - }); - ext = [ext[0], (ext[1] - ext[0]) / 2, ext[1]]; - var cScale = d3.scale.linear() - .domain(ext) - .range(['red', 'grey', 'blue']) - .interpolate(d3.interpolateLab); - - var color = function (d) { - return cScale(d[fd.secondary_metric]); - }; - var container = d3.select(slice.selector); - var eff_height = fd.show_datatable ? (slice.height() / 2) : slice.height(); - - container.append('div') - .attr('id', 'parcoords_' + slice.container_id) - .style('height', eff_height + 'px') - .classed("parcoords", true); - - var parcoords = d3.parcoords()('#parcoords_' + slice.container_id) - .width(slice.width()) - .color(color) - .alpha(0.5) - .composite("darken") - .height(eff_height) - .data(payload.data) - .render() - .createAxes() - .shadows() - .reorderable() - .brushMode("1D-axes"); - - if (fd.show_datatable) { - // create data table, row hover highlighting - var grid = d3.divgrid(); - container.append("div") - .datum(data.slice(0, 10)) - .attr('id', "grid") - .call(grid) - .classed("parcoords", true) - .selectAll(".row") - .on({ - mouseover: function (d) { - parcoords.highlight([d]); - }, - mouseout: parcoords.unhighlight - }); - // update data table on brush event - parcoords.on("brush", function (d) { - d3.select("#grid") - .datum(d.slice(0, 10)) - .call(grid) - .selectAll(".row") - .on({ - mouseover: function (d) { - parcoords.highlight([d]); - }, - mouseout: parcoords.unhighlight - }); - }); - } - slice.done(); - }) - .fail(function (xhr) { - slice.error(xhr.responseText); - }); - } - - return { - render: refresh, - resize: refresh - }; -} - -module.exports = parallelCoordVis; diff --git a/panoramix/assets/visualizations/pivot_table.css b/panoramix/assets/visualizations/pivot_table.css deleted file mode 100644 index 8aef19a85922..000000000000 --- a/panoramix/assets/visualizations/pivot_table.css +++ /dev/null @@ -1,13 +0,0 @@ -.gridster .widget.pivot_table { - overflow: auto !important; -} - -.table tr>th { - padding: 1px 5px !important; - font-size: small !important; -} - -.table tr>td { - padding: 1px 5px !important; - font-size: small !important; -} diff --git a/panoramix/assets/visualizations/pivot_table.js b/panoramix/assets/visualizations/pivot_table.js deleted file mode 100644 index 795adbb29752..000000000000 --- a/panoramix/assets/visualizations/pivot_table.js +++ /dev/null @@ -1,31 +0,0 @@ -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; - -require('datatables'); -require('./pivot_table.css'); -require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css'); - -module.exports = function (slice) { - var container = slice.container; - var form_data = slice.data.form_data; - - function refresh() { - $.getJSON(slice.jsonEndpoint(), function (json) { - container.html(json.data); - if (form_data.groupby.length === 1) { - var table = container.find('table').DataTable({ - paging: false, - searching: false - }); - table.column('-1').order('desc').draw(); - } - slice.done(json); - }).fail(function (xhr) { - slice.error(xhr.responseText); - }); - } - return { - render: refresh, - resize: refresh - }; -}; diff --git a/panoramix/assets/visualizations/sankey.css b/panoramix/assets/visualizations/sankey.css deleted file mode 100644 index 9a2a0c88ae2e..000000000000 --- a/panoramix/assets/visualizations/sankey.css +++ /dev/null @@ -1,20 +0,0 @@ -.sankey .node rect { - cursor: move; - fill-opacity: .9; - shape-rendering: crispEdges; -} - -.sankey .node text { - pointer-events: none; - text-shadow: 0 1px 0 #fff; -} - -.sankey .link { - fill: none; - stroke: #000; - stroke-opacity: .2; -} - -.sankey .link:hover { - stroke-opacity: .5; -} diff --git a/panoramix/assets/visualizations/sankey.js b/panoramix/assets/visualizations/sankey.js deleted file mode 100644 index ade452ae4753..000000000000 --- a/panoramix/assets/visualizations/sankey.js +++ /dev/null @@ -1,140 +0,0 @@ -// CSS -require('./sankey.css'); -// JS -var px = window.px || require('../javascripts/modules/panoramix.js'); -var d3 = window.d3 || require('d3'); -d3.sankey = require('d3-sankey').sankey; - -function sankeyVis(slice) { - var div = d3.select(slice.selector); - - var render = function () { - var margin = { - top: 5, - right: 5, - bottom: 5, - left: 5 - }; - var width = slice.width() - margin.left - margin.right; - var height = slice.height() - margin.top - margin.bottom; - - var formatNumber = d3.format(",.0f"), - format = function (d) { - return formatNumber(d) + " TWh"; - }; - - var svg = div.append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - var sankey = d3.sankey() - .nodeWidth(15) - .nodePadding(10) - .size([width, height]); - - var path = sankey.link(); - - d3.json(slice.jsonEndpoint(), function (error, json) { - if (error !== null) { - slice.error(error.responseText); - return ''; - } - var links = json.data; - var nodes = {}; - // Compute the distinct nodes from the links. - links.forEach(function (link) { - link.source = nodes[link.source] || (nodes[link.source] = { name: link.source }); - link.target = nodes[link.target] || (nodes[link.target] = { name: link.target }); - link.value = Number(link.value); - }); - nodes = d3.values(nodes); - - sankey - .nodes(nodes) - .links(links) - .layout(32); - - var link = svg.append("g").selectAll(".link") - .data(links) - .enter().append("path") - .attr("class", "link") - .attr("d", path) - .style("stroke-width", function (d) { - return Math.max(1, d.dy); - }) - .sort(function (a, b) { - return b.dy - a.dy; - }); - - link.append("title") - .text(function (d) { - return d.source.name + " → " + d.target.name + "\n" + format(d.value); - }); - - var node = svg.append("g").selectAll(".node") - .data(nodes) - .enter().append("g") - .attr("class", "node") - .attr("transform", function (d) { - return "translate(" + d.x + "," + d.y + ")"; - }) - .call(d3.behavior.drag() - .origin(function (d) { - return d; - }) - .on("dragstart", function () { - this.parentNode.appendChild(this); - }) - .on("drag", dragmove)); - - node.append("rect") - .attr("height", function (d) { - return d.dy; - }) - .attr("width", sankey.nodeWidth()) - .style("fill", function (d) { - d.color = px.color.category21(d.name.replace(/ .*/, "")); - return d.color; - }) - .style("stroke", function (d) { - return d3.rgb(d.color).darker(2); - }) - .append("title") - .text(function (d) { - return d.name + "\n" + format(d.value); - }); - - node.append("text") - .attr("x", -6) - .attr("y", function (d) { - return d.dy / 2; - }) - .attr("dy", ".35em") - .attr("text-anchor", "end") - .attr("transform", null) - .text(function (d) { - return d.name; - }) - .filter(function (d) { - return d.x < width / 2; - }) - .attr("x", 6 + sankey.nodeWidth()) - .attr("text-anchor", "start"); - - function dragmove(d) { - d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")"); - sankey.relayout(); - link.attr("d", path); - } - slice.done(json); - }); - }; - return { - render: render, - resize: render - }; -} - -module.exports = sankeyVis; diff --git a/panoramix/assets/visualizations/sunburst.css b/panoramix/assets/visualizations/sunburst.css deleted file mode 100644 index d2636faaa396..000000000000 --- a/panoramix/assets/visualizations/sunburst.css +++ /dev/null @@ -1,39 +0,0 @@ -.sunburst text { - shape-rendering: crispEdges; -} -.sunburst path { - stroke: #333; - stroke-width: 0.5px; -} -.sunburst .center-label { - text-anchor: middle; - fill: #000; - pointer-events: none; -} -.sunburst .path-percent { - font-size: 4em; -} -.sunburst .path-metrics { - font-size: 1.75em; -} -.sunburst .path-ratio { - font-size: 1.2em; -} - -.sunburst .breadcrumbs text { - font-weight: 600; - font-size: 1.2em; - text-anchor: middle; - fill: #000; -} - -/* dashboard specific */ -.dashboard .sunburst text { - font-size: 1em; -} -.dashboard .sunburst .path-percent { - font-size: 2.5em; -} -.dashboard .sunburst .path-metrics { - font-size: 1em; -} diff --git a/panoramix/assets/visualizations/sunburst.js b/panoramix/assets/visualizations/sunburst.js deleted file mode 100644 index 1e614ce946a1..000000000000 --- a/panoramix/assets/visualizations/sunburst.js +++ /dev/null @@ -1,359 +0,0 @@ -var d3 = window.d3 || require('d3'); -var px = require('../javascripts/modules/panoramix.js'); -var wrapSvgText = require('../javascripts/modules/utils.js').wrapSvgText; - -require('./sunburst.css'); - -// Modified from http://bl.ocks.org/kerryrodden/7090426 -function sunburstVis(slice) { - var container = d3.select(slice.selector); - - var render = function () { - // vars with shared scope within this function - var margin = { top: 10, right: 5, bottom: 10, left: 5 }; - var containerWidth = slice.width(); - var containerHeight = slice.height(); - var breadcrumbHeight = containerHeight * 0.085; - var visWidth = containerWidth - margin.left - margin.right; - var visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight; - var radius = Math.min(visWidth, visHeight) / 2; - var colorByCategory = true; // color by category if primary/secondary metrics match - - var maxBreadcrumbs, breadcrumbDims, // set based on data - totalSize, // total size of all segments; set after loading the data. - colorScale, - breadcrumbs, vis, arcs, gMiddleText; // dom handles - - // Helper + path gen functions - var partition = d3.layout.partition() - .size([2 * Math.PI, radius * radius]) - .value(function (d) { return d.m1; }); - - var arc = d3.svg.arc() - .startAngle(function (d) { - return d.x; - }) - .endAngle(function (d) { - return d.x + d.dx; - }) - .innerRadius(function (d) { - return Math.sqrt(d.y); - }) - .outerRadius(function (d) { - return Math.sqrt(d.y + d.dy); - }); - - var f = d3.format(".3s"); - var fp = d3.format(".3p"); - - container.select("svg").remove(); - - var svg = container.append("svg:svg") - .attr("width", containerWidth) - .attr("height", containerHeight); - - d3.json(slice.jsonEndpoint(), function (error, rawData) { - if (error !== null) { - slice.error(error.responseText); - return ''; - } - - createBreadcrumbs(rawData); - createVisualization(rawData); - - slice.done(rawData); - }); - - function createBreadcrumbs(rawData) { - var firstRowData = rawData.data[0]; - maxBreadcrumbs = (firstRowData.length - 2) + 1; // -2 bc row contains 2x metrics, +extra for %label and buffer - - breadcrumbDims = { - width: visWidth / maxBreadcrumbs, - height: breadcrumbHeight *0.8, // more margin - spacing: 3, - tipTailWidth: 10 - }; - - breadcrumbs = svg.append("svg:g") - .attr("class", "breadcrumbs") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - breadcrumbs.append("svg:text") - .attr("class", "end-label"); - } - - // Main function to draw and set up the visualization, once we have the data. - function createVisualization(rawData) { - var tree = buildHierarchy(rawData.data); - - vis = svg.append("svg:g") - .attr("class", "sunburst-vis") - .attr("transform", "translate(" + (margin.left + (visWidth / 2)) + "," + (margin.top + breadcrumbHeight + (visHeight / 2)) + ")") - .on("mouseleave", mouseleave); - - arcs = vis.append("svg:g") - .attr("id", "arcs"); - - gMiddleText = vis.append("svg:g") - .attr("class", "center-label"); - - // Bounding circle underneath the sunburst, to make it easier to detect - // when the mouse leaves the parent g. - arcs.append("svg:circle") - .attr("r", radius) - .style("opacity", 0); - - // For efficiency, filter nodes to keep only those large enough to see. - var nodes = partition.nodes(tree) - .filter(function (d) { - return (d.dx > 0.005); // 0.005 radians = 0.29 degrees - }); - - var ext; - - if (rawData.form_data.metric !== rawData.form_data.secondary_metric) { - colorByCategory = false; - - ext = d3.extent(nodes, function (d) { - return d.m2 / d.m1; - }); - - colorScale = d3.scale.linear() - .domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]]) - .range(["#00D1C1", "white", "#FFB400"]); - } - - var path = arcs.data([tree]).selectAll("path") - .data(nodes) - .enter().append("svg:path") - .attr("display", function (d) { - return d.depth ? null : "none"; - }) - .attr("d", arc) - .attr("fill-rule", "evenodd") - .style("fill", function (d) { - return colorByCategory ? px.color.category21(d.name) : colorScale(d.m2 / d.m1); - }) - .style("opacity", 1) - .on("mouseenter", mouseenter); - - // Get total size of the tree = value of root node from partition. - totalSize = path.node().__data__.value; - } - - // Fade all but the current sequence, and show it in the breadcrumb trail. - function mouseenter(d) { - - var percentage = (d.m1 / totalSize).toPrecision(3); - var percentageString = fp(percentage); - var metricsMatch = Math.abs(d.m1 - d.m2) < 0.000001; - - gMiddleText.selectAll("*").remove(); - - gMiddleText.append("text") - .attr("class", "path-percent") - .attr("y", "-10") - .text(percentageString); - - gMiddleText.append("text") - .attr("class", "path-metrics") - .attr("y", "25") - .text("m1: " + f(d.m1) + (metricsMatch ? "" : ", m2: " + f(d.m2))); - - gMiddleText.append("text") - .attr("class", "path-ratio") - .attr("y", "50") - .text("m2/m1: " + fp(d.m2 / d.m1)); - - var sequenceArray = getAncestors(d); - - // Reset and fade all the segments. - arcs.selectAll("path") - .style("stroke-width", null) - .style("stroke", null) - .style("opacity", 0.3); - - // Then highlight only those that are an ancestor of the current segment. - arcs.selectAll("path") - .filter(function (node) { - return (sequenceArray.indexOf(node) >= 0); - }) - .style("opacity", 1) - .style("stroke-width", "2px") - .style("stroke", "#000"); - - updateBreadcrumbs(sequenceArray, percentageString); - } - - // Restore everything to full opacity when moving off the visualization. - function mouseleave(d) { - - // Hide the breadcrumb trail - breadcrumbs.style("visibility", "hidden"); - - gMiddleText.selectAll("*").remove(); - - // Deactivate all segments during transition. - arcs.selectAll("path").on("mouseenter", null); - //gMiddleText.selectAll("*").remove(); - - // Transition each segment to full opacity and then reactivate it. - arcs.selectAll("path") - .transition() - .duration(200) - .style("opacity", 1) - .style("stroke", null) - .style("stroke-width", null) - .each("end", function () { - d3.select(this).on("mouseenter", mouseenter); - }); - } - - // Given a node in a partition layout, return an array of all of its ancestor - // nodes, highest first, but excluding the root. - function getAncestors(node) { - var path = []; - var current = node; - while (current.parent) { - path.unshift(current); - current = current.parent; - } - return path; - } - - // Generate a string that describes the points of a breadcrumb polygon. - function breadcrumbPoints(d, i) { - var points = []; - points.push("0,0"); - points.push(breadcrumbDims.width + ",0"); - points.push(breadcrumbDims.width + breadcrumbDims.tipTailWidth + "," + (breadcrumbDims.height / 2)); - points.push(breadcrumbDims.width+ "," + breadcrumbDims.height); - points.push("0," + breadcrumbDims.height); - if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex. - points.push(breadcrumbDims.tipTailWidth + "," + (breadcrumbDims.height / 2)); - } - return points.join(" "); - } - - function updateBreadcrumbs(sequenceArray, percentageString) { - var g = breadcrumbs.selectAll("g") - .data(sequenceArray, function (d) { - return d.name + d.depth; - }); - - // Add breadcrumb and label for entering nodes. - var entering = g.enter().append("svg:g"); - - entering.append("svg:polygon") - .attr("points", breadcrumbPoints) - .style("fill", function (d) { - return colorByCategory ? px.color.category21(d.name) : colorScale(d.m2 / d.m1); - }); - - entering.append("svg:text") - .attr("x", (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2) - .attr("y", breadcrumbDims.height / 4) - .attr("dy", "0.35em") - .attr("class", "step-label") - .text(function (d) { return d.name; }) - .call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2); - - // Set position for entering and updating nodes. - g.attr("transform", function (d, i) { - return "translate(" + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ", 0)"; - }); - - // Remove exiting nodes. - g.exit().remove(); - - // Now move and update the percentage at the end. - breadcrumbs.select(".end-label") - .attr("x", (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing)) - .attr("y", breadcrumbDims.height / 2) - .attr("dy", "0.35em") - .text(percentageString); - - // Make the breadcrumb trail visible, if it's hidden. - breadcrumbs.style("visibility", null); - } - - function buildHierarchy(rows) { - var root = { - name: "root", - children: [] - }; - for (var i = 0; i < rows.length; i++) { - var row = rows[i]; - var m1 = Number(row[row.length - 2]); - var m2 = Number(row[row.length - 1]); - var levels = row.slice(0, row.length - 2); - if (isNaN(m1)) { // e.g. if this is a header row - continue; - } - var currentNode = root; - for (var j = 0; j < levels.length; j++) { - var children = currentNode.children; - var nodeName = levels[j]; - // If the next node has the name "0", it will - var isLeafNode = (j >= levels.length - 1) || levels[j+1] === 0; - var childNode; - - if (!isLeafNode) { - // Not yet at the end of the sequence; move down the tree. - var foundChild = false; - for (var k = 0; k < children.length; k++) { - if (children[k].name === nodeName) { - childNode = children[k]; - foundChild = true; - break; - } - } - // If we don't already have a child node for this branch, create it. - if (!foundChild) { - childNode = { - name: nodeName, - children: [] - }; - children.push(childNode); - } - currentNode = childNode; - } else if (nodeName !== 0) { - // Reached the end of the sequence; create a leaf node. - childNode = { - name: nodeName, - m1: m1, - m2: m2 - }; - children.push(childNode); - } - } - } - - function recurse(node) { - if (node.children) { - var sums; - var m1 = 0; - var m2 = 0; - for (var i = 0; i < node.children.length; i++) { - sums = recurse(node.children[i]); - m1 += sums[0]; - m2 += sums[1]; - } - node.m1 = m1; - node.m2 = m2; - } - return [node.m1, node.m2]; - } - recurse(root); - return root; - } - }; - - return { - render: render, - resize: render - }; -} - -module.exports = sunburstVis; diff --git a/panoramix/assets/visualizations/table.css b/panoramix/assets/visualizations/table.css deleted file mode 100644 index cd70e14ebb51..000000000000 --- a/panoramix/assets/visualizations/table.css +++ /dev/null @@ -1,18 +0,0 @@ -.gridster .widget.table { - overflow: auto !important; -} - -.widget.table td.filtered { - background-color: #005a63; - color: white; -} - -.table tr>th { - padding: 1px 5px !important; - font-size: small !important; -} - -.table tr>td { - padding: 1px 5px !important; - font-size: small !important; -} diff --git a/panoramix/assets/visualizations/table.js b/panoramix/assets/visualizations/table.js deleted file mode 100644 index 937b0769bd19..000000000000 --- a/panoramix/assets/visualizations/table.js +++ /dev/null @@ -1,124 +0,0 @@ -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -var d3 = require('d3'); - -require('./table.css'); -require('datatables'); -require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css'); - -function tableVis(slice) { - var data = slice.data; - var form_data = data.form_data; - var f = d3.format('.3s'); - var fC = d3.format('0,000'); - - function refresh() { - $.getJSON(slice.jsonEndpoint(), onSuccess).fail(onError); - - function onError(xhr) { - slice.error(xhr.responseText); - } - - function onSuccess(json) { - var data = json.data; - var metrics = json.form_data.metrics; - - function col(c) { - var arr = []; - for (var i = 0; i < data.records.length; i++) { - arr.push(json.data.records[i][c]); - } - return arr; - } - var maxes = {}; - for (var i = 0; i < metrics.length; i++) { - maxes[metrics[i]] = d3.max(col(metrics[i])); - } - - var table = d3.select(slice.selector).append('table') - .classed('dataframe dataframe table table-striped table-bordered table-condensed table-hover dataTable no-footer', true); - - table.append('thead').append('tr') - .selectAll('th') - .data(data.columns).enter() - .append('th') - .text(function (d) { - return d; - }); - - table.append('tbody') - .selectAll('tr') - .data(data.records).enter() - .append('tr') - .selectAll('td') - .data(function (row, i) { - return data.columns.map(function (c) { - return { - col: c, - val: row[c], - isMetric: metrics.indexOf(c) >= 0 - }; - }); - }).enter() - .append('td') - .style('background-image', function (d) { - if (d.isMetric) { - var perc = Math.round((d.val / maxes[d.col]) * 100); - return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%"; - } - }) - .attr('title', function (d) { - if (!isNaN(d.val)) { - return fC(d.val); - } - }) - .attr('data-sort', function (d) { - if (d.isMetric) { - return d.val; - } - }) - .on("click", function (d) { - if (!d.isMetric) { - var td = d3.select(this); - if (td.classed('filtered')) { - slice.removeFilter(d.col, [d.val]); - d3.select(this).classed('filtered', false); - } else { - d3.select(this).classed('filtered', true); - slice.addFilter(d.col, [d.val]); - } - } - }) - .style("cursor", function (d) { - if (!d.isMetric) { - return 'pointer'; - } - }) - .html(function (d) { - if (d.isMetric) { - return f(d.val); - } else { - return d.val; - } - }); - var datatable = slice.container.find('.dataTable').DataTable({ - paging: false, - searching: form_data.include_search - }); - // Sorting table by main column - if (form_data.metrics.length > 0) { - var main_metric = form_data.metrics[0]; - datatable.column(data.columns.indexOf(main_metric)).order('desc').draw(); - } - slice.done(json); - slice.container.parents('.widget').find('.tooltip').remove(); - } - } - - return { - render: refresh, - resize: function () {} - }; -} - -module.exports = tableVis; diff --git a/panoramix/assets/visualizations/word_cloud.js b/panoramix/assets/visualizations/word_cloud.js deleted file mode 100644 index ab370b0bb1b2..000000000000 --- a/panoramix/assets/visualizations/word_cloud.js +++ /dev/null @@ -1,91 +0,0 @@ -var px = window.px || require('../javascripts/modules/panoramix.js'); -var d3 = window.d3 || require('d3'); -var cloudLayout = require('d3-cloud'); - -function wordCloudChart(slice) { - var chart = d3.select(slice.selector); - - function refresh() { - d3.json(slice.jsonEndpoint(), function (error, json) { - if (error !== null) { - slice.error(error.responseText); - return ''; - } - var data = json.data; - var range = [ - json.form_data.size_from, - json.form_data.size_to - ]; - var rotation = json.form_data.rotation; - var f_rotation; - if (rotation === "square") { - f_rotation = function () { - return ~~(Math.random() * 2) * 90; - }; - } else if (rotation === "flat") { - f_rotation = function () { - return 0; - }; - } else { - f_rotation = function () { - return (~~(Math.random() * 6) - 3) * 30; - }; - } - var size = [slice.width(), slice.height()]; - - var scale = d3.scale.linear() - .range(range) - .domain(d3.extent(data, function (d) { - return d.size; - })); - - var layout = cloudLayout() - .size(size) - .words(data) - .padding(5) - .rotate(f_rotation) - .font("serif") - .fontSize(function (d) { - return scale(d.size); - }) - .on("end", draw); - - layout.start(); - - function draw(words) { - chart.selectAll("*").remove(); - - chart.append("svg") - .attr("width", layout.size()[0]) - .attr("height", layout.size()[1]) - .append("g") - .attr("transform", "translate(" + layout.size()[0] / 2 + "," + layout.size()[1] / 2 + ")") - .selectAll("text") - .data(words) - .enter().append("text") - .style("font-size", function (d) { - return d.size + "px"; - }) - .style("font-family", "Impact") - .style("fill", function (d) { - return px.color.category21(d.text); - }) - .attr("text-anchor", "middle") - .attr("transform", function (d) { - return "translate(" + [d.x, d.y] + ") rotate(" + d.rotate + ")"; - }) - .text(function (d) { - return d.text; - }); - } - slice.done(data); - }); - } - - return { - render: refresh, - resize: refresh - }; -} - -module.exports = wordCloudChart; diff --git a/panoramix/assets/visualizations/world_map.css b/panoramix/assets/visualizations/world_map.css deleted file mode 100644 index 99e1bf6f0cc9..000000000000 --- a/panoramix/assets/visualizations/world_map.css +++ /dev/null @@ -1,7 +0,0 @@ -.world_map svg { - background-color: #feffff; -} - -.world_map { - position: relative; -} diff --git a/panoramix/assets/visualizations/world_map.js b/panoramix/assets/visualizations/world_map.js deleted file mode 100644 index 87f09b30be38..000000000000 --- a/panoramix/assets/visualizations/world_map.js +++ /dev/null @@ -1,110 +0,0 @@ -// JS -var d3 = window.d3 || require('d3'); -//var Datamap = require('../vendor/datamaps/datamaps.all.js'); -var Datamap = require('datamaps'); - -// CSS -require('./world_map.css'); - -function worldMapChart(slice) { - var render = function () { - var container = slice.container; - var div = d3.select(slice.selector); - - container.css('height', slice.height()); - - d3.json(slice.jsonEndpoint(), function (error, json) { - var fd = json.form_data; - - if (error !== null) { - slice.error(error.responseText); - return ''; - } - var ext = d3.extent(json.data, function (d) { - return d.m1; - }); - var extRadius = d3.extent(json.data, function (d) { - return d.m2; - }); - var radiusScale = d3.scale.linear() - .domain([extRadius[0], extRadius[1]]) - .range([1, fd.max_bubble_size]); - - json.data.forEach(function (d) { - d.radius = radiusScale(d.m2); - }); - - var colorScale = d3.scale.linear() - .domain([ext[0], ext[1]]) - .range(["#FFF", "black"]); - - var d = {}; - for (var i = 0; i < json.data.length; i++) { - var country = json.data[i]; - country.fillColor = colorScale(country.m1); - d[country.country] = country; - } - - var f = d3.format('.3s'); - - container.show(); - - var map = new Datamap({ - element: slice.container.get(0), - data: json.data, - fills: { - defaultFill: '#ddd' - }, - geographyConfig: { - popupOnHover: true, - highlightOnHover: true, - borderWidth: 1, - borderColor: '#fff', - highlightBorderColor: '#fff', - highlightFillColor: '#005a63', - highlightBorderWidth: 1, - popupTemplate: function (geo, data) { - return '
' + data.name + '
' + f(data.m1) + '
'; - } - }, - bubblesConfig: { - borderWidth: 1, - borderOpacity: 1, - borderColor: '#005a63', - popupOnHover: true, - radius: null, - popupTemplate: function (geo, data) { - return '
' + data.name + '
' + f(data.m2) + '
'; - }, - fillOpacity: 0.5, - animate: true, - highlightOnHover: true, - highlightFillColor: '#005a63', - highlightBorderColor: 'black', - highlightBorderWidth: 2, - highlightBorderOpacity: 1, - highlightFillOpacity: 0.85, - exitDelay: 100, - key: JSON.stringify - } - }); - - map.updateChoropleth(d); - - if (fd.show_bubbles) { - map.bubbles(json.data); - div.selectAll("circle.datamaps-bubble").style('fill', '#005a63'); - } - - slice.done(json); - - }); - }; - - return { - render: render, - resize: render - }; -} - -module.exports = worldMapChart; diff --git a/panoramix/assets/webpack.config.js b/panoramix/assets/webpack.config.js deleted file mode 100644 index 465a04e1c8ac..000000000000 --- a/panoramix/assets/webpack.config.js +++ /dev/null @@ -1,51 +0,0 @@ -var path = require('path'); -var APP_DIR = path.resolve(__dirname, './'); // input -var BUILD_DIR = path.resolve(__dirname, './javascripts/dist'); // output - -var config = { - // for now generate one compiled js file per entry point / html page - entry: { - 'css-theme': APP_DIR + '/javascripts/css-theme.js', - dashboard: APP_DIR + '/javascripts/dashboard.js', - explore: APP_DIR + '/javascripts/explore.js', - featured: APP_DIR + '/javascripts/featured.js', - sql: APP_DIR + '/javascripts/sql.js', - standalone: APP_DIR + '/javascripts/standalone.js' - }, - output: { - path: BUILD_DIR, - filename: '[name].entry.js' - }, - module: { - loaders: [ - { - test: /\.jsx?/, - include: APP_DIR, - exclude: APP_DIR + '/node_modules', - loader: 'babel' - }, - /* for require('*.css') */ - { - test: /\.css$/, - include: APP_DIR, - loader: "style-loader!css-loader" - }, - /* for css linking images */ - { test: /\.png$/, loader: "url-loader?limit=100000" }, - { test: /\.jpg$/, loader: "file-loader" }, - { test: /\.gif$/, loader: "file-loader" }, - /* for font-awesome */ - { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" }, - { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" }, - /* for require('*.less') */ - { - test: /\.less$/, - include: APP_DIR, - loader: "style!css!less" - } - ] - }, - plugins: [] -}; - -module.exports = config; diff --git a/panoramix/bin/__init__.py b/panoramix/bin/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/panoramix/bin/panoramix b/panoramix/bin/panoramix deleted file mode 100755 index e6d7a37231b1..000000000000 --- a/panoramix/bin/panoramix +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python - -from datetime import datetime -import logging -from subprocess import Popen - -from flask.ext.script import Manager -from panoramix import app -from flask.ext.migrate import MigrateCommand -import panoramix -from panoramix import db -from panoramix import data, utils - -config = app.config - -manager = Manager(app) -manager.add_command('db', MigrateCommand) - - -@manager.option( - '-d', '--debug', action='store_true', - help="Start the web server in debug mode") -@manager.option( - '-p', '--port', default=config.get("PANORAMIX_WEBSERVER_PORT"), - help="Specify the port on which to run the web server") -@manager.option( - '-w', '--workers', default=config.get("PANORAMIX_WORKERS", 16), - help="Number of gunicorn web server workers to fire up") -@manager.option( - '-t', '--timeout', default=config.get("PANORAMIX_WEBSERVER_TIMEOUT"), - help="Specify the timeout (seconds) for the gunicorn web server") -def runserver(debug, port, timeout, workers): - """Starts a Panoramix web server""" - debug = debug or config.get("DEBUG") - if debug: - app.run( - host='0.0.0.0', - port=int(port), - debug=True) - else: - cmd = ( - "gunicorn " - "-w {workers} " - "--timeout {timeout} " - "-b 0.0.0.0:{port} " - "panoramix:app").format(**locals()) - print("Starting server with command: " + cmd) - Popen(cmd, shell=True).wait() - -@manager.command -def init(): - """Inits the Panoramix application""" - utils.init(panoramix) - -@manager.option( - '-s', '--sample', action='store_true', - help="Only load 1000 rows (faster, used for testing)") -def load_examples(sample): - """Loads a set of Slices and Dashboards and a supporting dataset """ - print("Loading examples into {}".format(db)) - - data.load_css_templates() - - print("Loading [World Bank's Health Nutrition and Population Stats]") - data.load_world_bank_health_n_pop() - - print("Loading [Birth names]") - data.load_birth_names() - -@manager.command -def refresh_druid(): - """Refresh all druid datasources""" - session = db.session() - from panoramix import models - for cluster in session.query(models.DruidCluster).all(): - try: - cluster.refresh_datasources() - except Exception as e: - print( - "Error while processing cluster '{}'\n{}".format( - cluster, str(e))) - logging.exception(e) - cluster.metadata_last_refreshed = datetime.now() - print( - "Refreshed metadata from cluster " - "[" + cluster.cluster_name + "]") - session.commit() - - -if __name__ == "__main__": - manager.run() diff --git a/panoramix/config.py b/panoramix/config.py deleted file mode 100644 index 4980a06cf862..000000000000 --- a/panoramix/config.py +++ /dev/null @@ -1,118 +0,0 @@ -""" -All configuration in this file can be overridden by providing a local_config -in your PYTHONPATH. - -There' a ``from local_config import *`` at the end of this file. -""" -import os -from flask_appbuilder.security.manager import AUTH_DB -# from flask_appbuilder.security.manager import ( -# AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH) -BASE_DIR = os.path.abspath(os.path.dirname(__file__)) -from dateutil import tz - - -# --------------------------------------------------------- -# Panoramix specifix config -# --------------------------------------------------------- -ROW_LIMIT = 50000 -WEBSERVER_THREADS = 8 - -PANORAMIX_WEBSERVER_PORT = 8088 -PANORAMIX_WEBSERVER_TIMEOUT = 60 - -CUSTOM_SECURITY_MANAGER = None -# --------------------------------------------------------- - -# Your App secret key -SECRET_KEY = '\2\1thisismyscretkey\1\2\e\y\y\h' # noqa - -# The SQLAlchemy connection string. -SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/panoramix.db' -# SQLALCHEMY_DATABASE_URI = 'mysql://myapp@localhost/myapp' -# SQLALCHEMY_DATABASE_URI = 'postgresql://root:password@localhost/myapp' - -# Flask-WTF flag for CSRF -CSRF_ENABLED = True - -# Whether to run the web server in debug mode or not -DEBUG = True - -# Whether to show the stacktrace on 500 error -SHOW_STACKTRACE = True - -# ------------------------------ -# GLOBALS FOR APP Builder -# ------------------------------ -# Uncomment to setup Your App name -APP_NAME = "Panoramix" - -# Uncomment to setup Setup an App icon -# APP_ICON = "/static/img/something.png" - -# Druid query timezone -# tz.tzutc() : Using utc timezone -# tz.tzlocal() : Using local timezone -# other tz can be overridden by providing a local_config -DRUID_TZ = tz.tzutc() - -# ---------------------------------------------------- -# AUTHENTICATION CONFIG -# ---------------------------------------------------- -# The authentication type -# AUTH_OID : Is for OpenID -# AUTH_DB : Is for database (username/password() -# AUTH_LDAP : Is for LDAP -# AUTH_REMOTE_USER : Is for using REMOTE_USER from web server -AUTH_TYPE = AUTH_DB - -# Uncomment to setup Full admin role name -# AUTH_ROLE_ADMIN = 'Admin' - -# Uncomment to setup Public role name, no authentication needed -# AUTH_ROLE_PUBLIC = 'Public' - -# Will allow user self registration -# AUTH_USER_REGISTRATION = True - -# The default user self registration role -# AUTH_USER_REGISTRATION_ROLE = "Public" - -# When using LDAP Auth, setup the ldap server -# AUTH_LDAP_SERVER = "ldap://ldapserver.new" - -# Uncomment to setup OpenID providers example for OpenID authentication -# OPENID_PROVIDERS = [ -# { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' }, -# { 'name': 'AOL', 'url': 'http://openid.aol.com/' }, -# { 'name': 'Flickr', 'url': 'http://www.flickr.com/' }, -# { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }] -# --------------------------------------------------- -# Babel config for translations -# --------------------------------------------------- -# Setup default language -BABEL_DEFAULT_LOCALE = 'en' -# Your application default translation path -BABEL_DEFAULT_FOLDER = 'translations' -# The allowed translation for you app -LANGUAGES = { - 'en': {'flag': 'us', 'name': 'English'}, -} -# --------------------------------------------------- -# Image and file configuration -# --------------------------------------------------- -# The file upload folder, when using models with files -UPLOAD_FOLDER = BASE_DIR + '/app/static/uploads/' - -# The image upload folder, when using models with images -IMG_UPLOAD_FOLDER = BASE_DIR + '/app/static/uploads/' - -# The image upload url, when using models with images -IMG_UPLOAD_URL = '/static/uploads/' -# Setup image size default is (300, 200, True) -# IMG_SIZE = (300, 200, True) - -try: - from panoramix_config import * # noqa -except Exception: - pass diff --git a/panoramix/data/__init__.py b/panoramix/data/__init__.py deleted file mode 100644 index 8cd6798f3409..000000000000 --- a/panoramix/data/__init__.py +++ /dev/null @@ -1,624 +0,0 @@ -"""Loads datasets, dashboards and slices in a new panoramix instance""" - -import gzip -import json -import os -import textwrap - -import pandas as pd -from sqlalchemy import String, DateTime - -from panoramix import app, db, models, utils - -# Shortcuts -DB = models.Database -Slice = models.Slice -TBL = models.SqlaTable -Dash = models.Dashboard - -config = app.config - -DATA_FOLDER = os.path.join(config.get("BASE_DIR"), 'data') - - -def get_or_create_db(session): - print("Creating database reference") - dbobj = session.query(DB).filter_by(database_name='main').first() - if not dbobj: - dbobj = DB(database_name="main") - print(config.get("SQLALCHEMY_DATABASE_URI")) - dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI") - session.add(dbobj) - session.commit() - return dbobj - - -def merge_slice(slc): - o = db.session.query(Slice).filter_by(slice_name=slc.slice_name).first() - if o: - db.session.delete(o) - db.session.add(slc) - db.session.commit() - - -def get_slice_json(defaults, **kwargs): - d = defaults.copy() - d.update(kwargs) - return json.dumps(d, indent=4, sort_keys=True) - - -def load_world_bank_health_n_pop(): - """Loads the world bank health dataset, slices and a dashboard""" - tbl_name = 'wb_health_population' - with gzip.open(os.path.join(DATA_FOLDER, 'countries.json.gz')) as f: - pdf = pd.read_json(f) - pdf.columns = [col.replace('.', '_') for col in pdf.columns] - pdf.year = pd.to_datetime(pdf.year) - pdf.to_sql( - tbl_name, - db.engine, - if_exists='replace', - chunksize=500, - dtype={ - 'year': DateTime(), - 'country_code': String(3), - 'country_name': String(255), - 'region': String(255), - }, - index=False) - - print("Creating table [wb_health_population] reference") - tbl = db.session.query(TBL).filter_by(table_name=tbl_name).first() - if not tbl: - tbl = TBL(table_name=tbl_name) - tbl.description = utils.readfile(os.path.join(DATA_FOLDER, 'countries.md')) - tbl.main_dttm_col = 'year' - tbl.is_featured = True - tbl.database = get_or_create_db(db.session) - db.session.merge(tbl) - db.session.commit() - tbl.fetch_metadata() - - defaults = { - "compare_lag": "10", - "compare_suffix": "o10Y", - "datasource_id": "1", - "datasource_name": "birth_names", - "datasource_type": "table", - "limit": "25", - "granularity": "year", - "groupby": [], - "metric": 'sum__SP_POP_TOTL', - "metrics": ["sum__SP_POP_TOTL"], - "row_limit": config.get("ROW_LIMIT"), - "since": "2014-01-01", - "until": "2014-01-01", - "where": "", - "markup_type": "markdown", - "country_fieldtype": "cca3", - "secondary_metric": "sum__SP_POP_TOTL", - "entity": "country_code", - "show_bubbles": "y", - } - - print("Creating slices") - slices = [ - Slice( - slice_name="Region Filter", - viz_type='filter_box', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type='filter_box', - groupby=['region'], - )), - Slice( - slice_name="World's Population", - viz_type='big_number', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - since='2000', - viz_type='big_number', - compare_lag="10", - metric='sum__SP_POP_TOTL', - compare_suffix="over 10Y")), - Slice( - slice_name="Most Populated Countries", - viz_type='table', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type='table', - metrics=["sum__SP_POP_TOTL"], - groupby=['country_name'])), - Slice( - slice_name="Growth Rate", - viz_type='line', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type='line', - since="1960-01-01", - metrics=["sum__SP_POP_TOTL"], - num_period_compare="10", - groupby=['country_name'])), - Slice( - slice_name="% Rural", - viz_type='world_map', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type='world_map', - metric= "sum__SP_RUR_TOTL_ZS", - num_period_compare="10",)), - Slice( - slice_name="Life Expexctancy VS Rural %", - viz_type='bubble', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type='bubble', - since= "2011-01-01", - until= "2011-01-01", - series="region", - limit="0", - entity="country_name", - x="sum__SP_RUR_TOTL_ZS", - y="sum__SP_DYN_LE00_IN", - size="sum__SP_POP_TOTL", - max_bubble_size="50", - flt_col_1="country_code", - flt_op_1= "not in", - flt_eq_1="TCA,MNP,DMA,MHL,MCO,SXM,CYM,TUV,IMY,KNA,ASM,ADO,AMA,PLW", - num_period_compare="10",)), - Slice( - slice_name="Rural Breakdown", - viz_type='sunburst', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type='sunburst', - groupby=["region", "country_name"], - secondary_metric="sum__SP_RUR_TOTL", - since= "2011-01-01", - until= "2011-01-01",)), - Slice( - slice_name="World's Pop Growth", - viz_type='area', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - since="1960-01-01", - until="now", - viz_type='area', - groupby=["region"],)), - ] - for slc in slices: - merge_slice(slc) - - print("Creating a World's Health Bank dashboard") - dash_name = "World's Health Bank Dashboard" - dash = db.session.query(Dash).filter_by(dashboard_title=dash_name).first() - - if dash: - db.session.delete(dash) - js = """\ -[ - { - "size_y": 1, - "size_x": 3, - "col": 1, - "slice_id": "269", - "row": 1 - }, - { - "size_y": 3, - "size_x": 3, - "col": 1, - "slice_id": "270", - "row": 2 - }, - { - "size_y": 7, - "size_x": 3, - "col": 10, - "slice_id": "271", - "row": 1 - }, - { - "size_y": 3, - "size_x": 6, - "col": 1, - "slice_id": "272", - "row": 5 - }, - { - "size_y": 4, - "size_x": 6, - "col": 4, - "slice_id": "273", - "row": 1 - }, - { - "size_y": 4, - "size_x": 6, - "col": 7, - "slice_id": "274", - "row": 8 - }, - { - "size_y": 3, - "size_x": 3, - "col": 7, - "slice_id": "275", - "row": 5 - }, - { - "size_y": 4, - "size_x": 6, - "col": 1, - "slice_id": "276", - "row": 8 - } -] - """ - l = json.loads(js) - for i, pos in enumerate(l): - pos['slice_id'] = str(slices[i].id) - dash = Dash( - dashboard_title=dash_name, - position_json=json.dumps(l, indent=4), - slug="world_health", - ) - for s in slices: - dash.slices.append(s) - db.session.commit() - - -def load_css_templates(): - """Loads 2 css templates to demonstrate the feature""" - print('Creating default CSS templates') - CSS = models.CssTemplate - - obj = db.session.query(CSS).filter_by(template_name='Flat').first() - if not obj: - obj = CSS(template_name="Flat") - css = textwrap.dedent("""\ - .gridster li.widget { - transition: background-color 0.5s ease; - background-color: #FAFAFA; - border: 1px solid #CCC; - overflow: hidden; - box-shadow: none; - border-radius: 0px; - } - .gridster li.widget:hover { - border: 1px solid #000; - background-color: #EAEAEA; - } - .navbar { - transition: opacity 0.5s ease; - opacity: 0.05; - } - .navbar:hover { - opacity: 1; - } - .chart-header .header{ - font-weight: normal; - font-size: 12px; - } - /* - var bnbColors = [ - //rausch hackb kazan babu lima beach tirol - '#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c', - '#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a', - '#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e', - ]; - */ - """) - obj.css = css - db.session.merge(obj) - db.session.commit() - - obj = ( - db.session.query(CSS).filter_by(template_name='Courier Black').first()) - if not obj: - obj = CSS(template_name="Courier Black") - css = textwrap.dedent("""\ - .gridster li.widget { - transition: background-color 0.5s ease; - background-color: #EEE; - border: 2px solid #444; - overflow: hidden; - border-radius: 15px; - box-shadow: none; - } - h2 { - color: white; - font-size: 52px; - } - .navbar { - box-shadow: none; - } - .gridster li.widget:hover { - border: 2px solid #000; - background-color: #EAEAEA; - } - .navbar { - transition: opacity 0.5s ease; - opacity: 0.05; - } - .navbar:hover { - opacity: 1; - } - .chart-header .header{ - font-weight: normal; - font-size: 12px; - } - .nvd3 text { - font-size: 12px; - font-family: inherit; - } - body{ - background: #000; - font-family: Courier, Monaco, monospace;; - } - /* - var bnbColors = [ - //rausch hackb kazan babu lima beach tirol - '#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c', - '#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a', - '#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e', - ]; - */ - """) - obj.css = css - db.session.merge(obj) - db.session.commit() - - -def load_birth_names(): - with gzip.open(os.path.join(DATA_FOLDER, 'birth_names.json.gz')) as f: - pdf = pd.read_json(f) - pdf.ds = pd.to_datetime(pdf.ds, unit='ms') - pdf.to_sql( - 'birth_names', - db.engine, - if_exists='replace', - chunksize=500, - dtype={ - 'ds': DateTime, - 'gender': String(16), - 'state': String(10), - 'name': String(255), - }, - index=False) - l = [] - print("Done loading table!") - print("-" * 80) - - print("Creating table reference") - obj = db.session.query(TBL).filter_by(table_name='birth_names').first() - if not obj: - obj = TBL(table_name = 'birth_names') - obj.main_dttm_col = 'ds' - obj.database = get_or_create_db(db.session) - obj.is_featured = True - db.session.merge(obj) - db.session.commit() - obj.fetch_metadata() - tbl = obj - - defaults = { - "compare_lag": "10", - "compare_suffix": "o10Y", - "datasource_id": "1", - "datasource_name": "birth_names", - "datasource_type": "table", - "flt_op_1": "in", - "limit": "25", - "granularity": "ds", - "groupby": [], - "metric": 'sum__num', - "metrics": ["sum__num"], - "row_limit": config.get("ROW_LIMIT"), - "since": "100 years ago", - "until": "now", - "viz_type": "table", - "where": "", - "markup_type": "markdown", - } - - print("Creating some slices") - slices = [ - Slice( - slice_name="Girls", - viz_type='table', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - groupby=['name'], - flt_col_1='gender', - flt_eq_1="girl", row_limit=50)), - Slice( - slice_name="Boys", - viz_type='table', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - groupby=['name'], - flt_col_1='gender', - flt_eq_1="boy", - row_limit=50)), - Slice( - slice_name="Participants", - viz_type='big_number', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type="big_number", granularity="ds", - compare_lag="5", compare_suffix="over 5Y")), - Slice( - slice_name="Genders", - viz_type='pie', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type="pie", groupby=['gender'])), - Slice( - slice_name="Genders by State", - viz_type='dist_bar', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - flt_eq_1="other", viz_type="dist_bar", - metrics=['sum__sum_girls', 'sum__sum_boys'], - groupby=['state'], flt_op_1='not in', flt_col_1='state')), - Slice( - slice_name="Trends", - viz_type='line', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type="line", groupby=['name'], - granularity='ds', rich_tooltip='y', show_legend='y')), - Slice( - slice_name="Title", - viz_type='markup', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type="markup", markup_type="html", - code="""\ -
-

Birth Names Dashboard

-

- The source dataset came from - [here] -

- -
-""" - )), - Slice( - slice_name="Name Cloud", - viz_type='word_cloud', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type="word_cloud", size_from="10", - series='name', size_to="70", rotation="square", - limit='100')), - Slice( - slice_name="Pivot Table", - viz_type='pivot_table', - datasource_type='table', - table=tbl, - params=get_slice_json( - defaults, - viz_type="pivot_table", metrics=['sum__num'], - groupby=['name'], columns=['state'])), - ] - for slc in slices: - merge_slice(slc) - - print("Creating a dashboard") - dash = db.session.query(Dash).filter_by(dashboard_title="Births").first() - - if dash: - db.session.delete(dash) - js = """ -[ - { - "size_y": 4, - "size_x": 2, - "col": 8, - "slice_id": "85", - "row": 7 - }, - { - "size_y": 4, - "size_x": 2, - "col": 10, - "slice_id": "86", - "row": 7 - }, - { - "size_y": 2, - "size_x": 2, - "col": 1, - "slice_id": "87", - "row": 1 - }, - { - "size_y": 2, - "size_x": 2, - "col": 3, - "slice_id": "88", - "row": 1 - }, - { - "size_y": 3, - "size_x": 7, - "col": 5, - "slice_id": "89", - "row": 4 - }, - { - "size_y": 4, - "size_x": 7, - "col": 1, - "slice_id": "90", - "row": 7 - }, - { - "size_y": 3, - "size_x": 3, - "col": 9, - "slice_id": "91", - "row": 1 - }, - { - "size_y": 3, - "size_x": 4, - "col": 5, - "slice_id": "92", - "row": 1 - }, - { - "size_y": 4, - "size_x": 4, - "col": 1, - "slice_id": "93", - "row": 3 - } -] - """ - l = json.loads(js) - for i, pos in enumerate(l): - pos['slice_id'] = str(slices[i].id) - dash = Dash( - dashboard_title="Births", - position_json=json.dumps(l, indent=4), - slug="births", - ) - for s in slices: - dash.slices.append(s) - db.session.commit() diff --git a/panoramix/data/birth_names.csv.gz b/panoramix/data/birth_names.csv.gz deleted file mode 100644 index 9990ab9ccb65..000000000000 Binary files a/panoramix/data/birth_names.csv.gz and /dev/null differ diff --git a/panoramix/data/birth_names.json.gz b/panoramix/data/birth_names.json.gz deleted file mode 100644 index 2652cf7242a9..000000000000 Binary files a/panoramix/data/birth_names.json.gz and /dev/null differ diff --git a/panoramix/data/countries.json.gz b/panoramix/data/countries.json.gz deleted file mode 100644 index 6c71c0c43232..000000000000 Binary files a/panoramix/data/countries.json.gz and /dev/null differ diff --git a/panoramix/data/countries.md b/panoramix/data/countries.md deleted file mode 100644 index 253a68b9f50a..000000000000 --- a/panoramix/data/countries.md +++ /dev/null @@ -1,355 +0,0 @@ -This data was download from the -[World's Health Organization's website](http://data.worldbank.org/data-catalog/health-nutrition-and-population-statistics) - -Here's the script that was used to massage the data: - - DIR = "" - df_country = pd.read_csv(DIR + '/HNP_Country.csv') - df_country.columns = ['country_code'] + list(df_country.columns[1:]) - df_country = df_country[['country_code', 'Region']] - df_country.columns = ['country_code', 'region'] - - df = pd.read_csv(DIR + '/HNP_Data.csv') - del df['Unnamed: 60'] - df.columns = ['country_name', 'country_code'] + list(df.columns[2:]) - ndf = df.merge(df_country, how='inner') - - dims = ('country_name', 'country_code', 'region') - vv = [str(i) for i in range(1960, 2015)] - mdf = pd.melt(ndf, id_vars=dims + ('Indicator Code',), value_vars=vv) - mdf['year'] = mdf.variable + '-01-01' - dims = dims + ('year',) - - pdf = mdf.pivot_table(values='value', columns='Indicator Code', index=dims) - pdf = pdf.reset_index() - pdf.to_csv(DIR + '/countries.csv') - pdf.to_json(DIR + '/countries.json', orient='records') - -Here's the description of the metrics available: - -Series | Code Indicator Name ---- | --- -NY.GNP.PCAP.CD | GNI per capita, Atlas method (current US$) -SE.ADT.1524.LT.FM.ZS | Literacy rate, youth (ages 15-24), gender parity index (GPI) -SE.ADT.1524.LT.MA.ZS | Literacy rate, youth male (% of males ages 15-24) -SE.ADT.1524.LT.ZS | Literacy rate, youth total (% of people ages 15-24) -SE.ADT.LITR.FE.ZS | Literacy rate, adult female (% of females ages 15 and above) -SE.ADT.LITR.MA.ZS | Literacy rate, adult male (% of males ages 15 and above) -SE.ADT.LITR.ZS | Literacy rate, adult total (% of people ages 15 and above) -SE.ENR.ORPH | Ratio of school attendance of orphans to school attendance of non-orphans ages 10-14 -SE.PRM.CMPT.FE.ZS | Primary completion rate, female (% of relevant age group) -SE.PRM.CMPT.MA.ZS | Primary completion rate, male (% of relevant age group) -SE.PRM.CMPT.ZS | Primary completion rate, total (% of relevant age group) -SE.PRM.ENRR | School enrollment, primary (% gross) -SE.PRM.ENRR.FE | School enrollment, primary, female (% gross) -SE.PRM.ENRR.MA | School enrollment, primary, male (% gross) -SE.PRM.NENR | School enrollment, primary (% net) -SE.PRM.NENR.FE | School enrollment, primary, female (% net) -SE.PRM.NENR.MA | School enrollment, primary, male (% net) -SE.SEC.ENRR | School enrollment, secondary (% gross) -SE.SEC.ENRR.FE | School enrollment, secondary, female (% gross) -SE.SEC.ENRR.MA | School enrollment, secondary, male (% gross) -SE.SEC.NENR | School enrollment, secondary (% net) -SE.SEC.NENR.FE | School enrollment, secondary, female (% net) -SE.SEC.NENR.MA | School enrollment, secondary, male (% net) -SE.TER.ENRR | School enrollment, tertiary (% gross) -SE.TER.ENRR.FE | School enrollment, tertiary, female (% gross) -SE.XPD.TOTL.GD.ZS | Government expenditure on education, total (% of GDP) -SH.ANM.CHLD.ZS | Prevalence of anemia among children (% of children under 5) -SH.ANM.NPRG.ZS | Prevalence of anemia among non-pregnant women (% of women ages 15-49) -SH.CON.1524.FE.ZS | Condom use, population ages 15-24, female (% of females ages 15-24) -SH.CON.1524.MA.ZS | Condom use, population ages 15-24, male (% of males ages 15-24) -SH.CON.AIDS.FE.ZS | Condom use at last high-risk sex, adult female (% ages 15-49) -SH.CON.AIDS.MA.ZS | Condom use at last high-risk sex, adult male (% ages 15-49) -SH.DTH.COMM.ZS | Cause of death, by communicable diseases and maternal, prenatal and nutrition conditions (% of total) -SH.DTH.IMRT | Number of infant deaths -SH.DTH.INJR.ZS | Cause of death, by injury (% of total) -SH.DTH.MORT | Number of under-five deaths -SH.DTH.NCOM.ZS | Cause of death, by non-communicable diseases (% of total) -SH.DTH.NMRT | Number of neonatal deaths -SH.DYN.AIDS | Adults (ages 15+) living with HIV -SH.DYN.AIDS.DH | AIDS estimated deaths (UNAIDS estimates) -SH.DYN.AIDS.FE.ZS | Women's share of population ages 15+ living with HIV (%) -SH.DYN.AIDS.ZS | Prevalence of HIV, total (% of population ages 15-49) -SH.DYN.MORT | Mortality rate, under-5 (per 1,000 live births) -SH.DYN.MORT.FE | Mortality rate, under-5, female (per 1,000 live births) -SH.DYN.MORT.MA | Mortality rate, under-5, male (per 1,000 live births) -SH.DYN.NMRT | Mortality rate, neonatal (per 1,000 live births) -SH.FPL.SATI.ZS | Met need for contraception (% of married women ages 15-49) -SH.H2O.SAFE.RU.ZS | Improved water source, rural (% of rural population with access) -SH.H2O.SAFE.UR.ZS | Improved water source, urban (% of urban population with access) -SH.H2O.SAFE.ZS | Improved water source (% of population with access) -SH.HIV.0014 | Children (0-14) living with HIV -SH.HIV.1524.FE.ZS | Prevalence of HIV, female (% ages 15-24) -SH.HIV.1524.KW.FE.ZS | Comprehensive correct knowledge of HIV/AIDS, ages 15-24, female (2 prevent ways and reject 3 misconceptions) -SH.HIV.1524.KW.MA.ZS | Comprehensive correct knowledge of HIV/AIDS, ages 15-24, male (2 prevent ways and reject 3 misconceptions) -SH.HIV.1524.MA.ZS | Prevalence of HIV, male (% ages 15-24) -SH.HIV.ARTC.ZS | Antiretroviral therapy coverage (% of people living with HIV) -SH.HIV.KNOW.FE.ZS | % of females ages 15-49 having comprehensive correct knowledge about HIV (2 prevent ways and reject 3 misconceptions) -SH.HIV.KNOW.MA.ZS | % of males ages 15-49 having comprehensive correct knowledge about HIV (2 prevent ways and reject 3 misconceptions) -SH.HIV.ORPH | Children orphaned by HIV/AIDS -SH.HIV.TOTL | Adults (ages 15+) and children (0-14 years) living with HIV -SH.IMM.HEPB | Immunization, HepB3 (% of one-year-old children) -SH.IMM.HIB3 | Immunization, Hib3 (% of children ages 12-23 months) -SH.IMM.IBCG | Immunization, BCG (% of one-year-old children) -SH.IMM.IDPT | Immunization, DPT (% of children ages 12-23 months) -SH.IMM.MEAS | Immunization, measles (% of children ages 12-23 months) -SH.IMM.POL3 | Immunization, Pol3 (% of one-year-old children) -SH.MED.BEDS.ZS | Hospital beds (per 1,000 people) -SH.MED.CMHW.P3 | Community health workers (per 1,000 people) -SH.MED.NUMW.P3 | Nurses and midwives (per 1,000 people) -SH.MED.PHYS.ZS | Physicians (per 1,000 people) -SH.MLR.NETS.ZS | Use of insecticide-treated bed nets (% of under-5 population) -SH.MLR.PREG.ZS | Use of any antimalarial drug (% of pregnant women) -SH.MLR.SPF2.ZS | Use of Intermittent Preventive Treatment of malaria, 2+ doses of SP/Fansidar (% of pregnant women) -SH.MLR.TRET.ZS | Children with fever receiving antimalarial drugs (% of children under age 5 with fever) -SH.MMR.DTHS | Number of maternal deaths -SH.MMR.LEVE | Number of weeks of maternity leave -SH.MMR.RISK | Lifetime risk of maternal death (1 in: rate varies by country) -SH.MMR.RISK.ZS | Lifetime risk of maternal death (%) -SH.MMR.WAGE.ZS | Maternal leave benefits (% of wages paid in covered period) -SH.PRG.ANEM | Prevalence of anemia among pregnant women (%) -SH.PRG.ARTC.ZS | Antiretroviral therapy coverage (% of pregnant women living with HIV) -SH.PRG.SYPH.ZS | Prevalence of syphilis (% of women attending antenatal care) -SH.PRV.SMOK.FE | Smoking prevalence, females (% of adults) -SH.PRV.SMOK.MA | Smoking prevalence, males (% of adults) -SH.STA.ACSN | Improved sanitation facilities (% of population with access) -SH.STA.ACSN.RU | Improved sanitation facilities, rural (% of rural population with access) -SH.STA.ACSN.UR | Improved sanitation facilities, urban (% of urban population with access) -SH.STA.ANV4.ZS | Pregnant women receiving prenatal care of at least four visits (% of pregnant women) -SH.STA.ANVC.ZS | Pregnant women receiving prenatal care (%) -SH.STA.ARIC.ZS | ARI treatment (% of children under 5 taken to a health provider) -SH.STA.BFED.ZS | Exclusive breastfeeding (% of children under 6 months) -SH.STA.BRTC.ZS | Births attended by skilled health staff (% of total) -SH.STA.BRTW.ZS | Low-birthweight babies (% of births) -SH.STA.DIAB.ZS | Diabetes prevalence (% of population ages 20 to 79) -SH.STA.IYCF.ZS | Infant and young child feeding practices, all 3 IYCF (% children ages 6-23 months) -SH.STA.MALN.FE.ZS | Prevalence of underweight, weight for age, female (% of children under 5) -SH.STA.MALN.MA.ZS | Prevalence of underweight, weight for age, male (% of children under 5) -SH.STA.MALN.ZS | Prevalence of underweight, weight for age (% of children under 5) -SH.STA.MALR | Malaria cases reported -SH.STA.MMRT | Maternal mortality ratio (modeled estimate, per 100,000 live births) -SH.STA.MMRT.NE | Maternal mortality ratio (national estimate, per 100,000 live births) -SH.STA.ORCF.ZS | Diarrhea treatment (% of children under 5 receiving oral rehydration and continued feeding) -SH.STA.ORTH | Diarrhea treatment (% of children under 5 who received ORS packet) -SH.STA.OW15.FE.ZS | Prevalence of overweight, female (% of female adults) -SH.STA.OW15.MA.ZS | Prevalence of overweight, male (% of male adults) -SH.STA.OW15.ZS | Prevalence of overweight (% of adults) -SH.STA.OWGH.FE.ZS | Prevalence of overweight, weight for height, female (% of children under 5) -SH.STA.OWGH.MA.ZS | Prevalence of overweight, weight for height, male (% of children under 5) -SH.STA.OWGH.ZS | Prevalence of overweight, weight for height (% of children under 5) -SH.STA.PNVC.ZS | Postnatal care coverage (% mothers) -SH.STA.STNT.FE.ZS | Prevalence of stunting, height for age, female (% of children under 5) -SH.STA.STNT.MA.ZS | Prevalence of stunting, height for age, male (% of children under 5) -SH.STA.STNT.ZS | Prevalence of stunting, height for age (% of children under 5) -SH.STA.WAST.FE.ZS | Prevalence of wasting, weight for height, female (% of children under 5) -SH.STA.WAST.MA.ZS | Prevalence of wasting, weight for height, male (% of children under 5) -SH.STA.WAST.ZS | Prevalence of wasting, weight for height (% of children under 5) -SH.SVR.WAST.FE.ZS | Prevalence of severe wasting, weight for height, female (% of children under 5) -SH.SVR.WAST.MA.ZS | Prevalence of severe wasting, weight for height, male (% of children under 5) -SH.SVR.WAST.ZS | Prevalence of severe wasting, weight for height (% of children under 5) -SH.TBS.CURE.ZS | Tuberculosis treatment success rate (% of new cases) -SH.TBS.DTEC.ZS | Tuberculosis case detection rate (%, all forms) -SH.TBS.INCD | Incidence of tuberculosis (per 100,000 people) -SH.TBS.MORT | Tuberculosis death rate (per 100,000 people) -SH.TBS.PREV | Prevalence of tuberculosis (per 100,000 population) -SH.VAC.TTNS.ZS | Newborns protected against tetanus (%) -SH.XPD.EXTR.ZS | External resources for health (% of total expenditure on health) -SH.XPD.OOPC.TO.ZS | Out-of-pocket health expenditure (% of total expenditure on health) -SH.XPD.OOPC.ZS | Out-of-pocket health expenditure (% of private expenditure on health) -SH.XPD.PCAP | Health expenditure per capita (current US$) -SH.XPD.PCAP.PP.KD | Health expenditure per capita, PPP (constant 2011 international $) -SH.XPD.PRIV | Health expenditure, private (% of total health expenditure) -SH.XPD.PRIV.ZS | Health expenditure, private (% of GDP) -SH.XPD.PUBL | Health expenditure, public (% of total health expenditure) -SH.XPD.PUBL.GX.ZS | Health expenditure, public (% of government expenditure) -SH.XPD.PUBL.ZS | Health expenditure, public (% of GDP) -SH.XPD.TOTL.CD | Health expenditure, total (current US$) -SH.XPD.TOTL.ZS | Health expenditure, total (% of GDP) -SI.POV.NAHC | Poverty headcount ratio at national poverty lines (% of population) -SI.POV.RUHC | Rural poverty headcount ratio at national poverty lines (% of rural population) -SI.POV.URHC | Urban poverty headcount ratio at national poverty lines (% of urban population) -SL.EMP.INSV.FE.ZS | Share of women in wage employment in the nonagricultural sector (% of total nonagricultural employment) -SL.TLF.TOTL.FE.ZS | Labor force, female (% of total labor force) -SL.TLF.TOTL.IN | Labor force, total -SL.UEM.TOTL.FE.ZS | Unemployment, female (% of female labor force) (modeled ILO estimate) -SL.UEM.TOTL.MA.ZS | Unemployment, male (% of male labor force) (modeled ILO estimate) -SL.UEM.TOTL.ZS | Unemployment, total (% of total labor force) (modeled ILO estimate) -SM.POP.NETM | Net migration -SN.ITK.DEFC | Number of people who are undernourished -SN.ITK.DEFC.ZS | Prevalence of undernourishment (% of population) -SN.ITK.SALT.ZS | Consumption of iodized salt (% of households) -SN.ITK.VITA.ZS | Vitamin A supplementation coverage rate (% of children ages 6-59 months) -SP.ADO.TFRT | Adolescent fertility rate (births per 1,000 women ages 15-19) -SP.DYN.AMRT.FE | Mortality rate, adult, female (per 1,000 female adults) -SP.DYN.AMRT.MA | Mortality rate, adult, male (per 1,000 male adults) -SP.DYN.CBRT.IN | Birth rate, crude (per 1,000 people) -SP.DYN.CDRT.IN | Death rate, crude (per 1,000 people) -SP.DYN.CONU.ZS | Contraceptive prevalence (% of women ages 15-49) -SP.DYN.IMRT.FE.IN | Mortality rate, infant, female (per 1,000 live births) -SP.DYN.IMRT.IN | Mortality rate, infant (per 1,000 live births) -SP.DYN.IMRT.MA.IN | Mortality rate, infant, male (per 1,000 live births) -SP.DYN.LE00.FE.IN | Life expectancy at birth, female (years) -SP.DYN.LE00.IN | Life expectancy at birth, total (years) -SP.DYN.LE00.MA.IN | Life expectancy at birth, male (years) -SP.DYN.SMAM.FE | Mean age at first marriage, female -SP.DYN.SMAM.MA | Mean age at first marriage, male -SP.DYN.TFRT.IN | Fertility rate, total (births per woman) -SP.DYN.TO65.FE.ZS | Survival to age 65, female (% of cohort) -SP.DYN.TO65.MA.ZS | Survival to age 65, male (% of cohort) -SP.DYN.WFRT | Wanted fertility rate (births per woman) -SP.HOU.FEMA.ZS | Female headed households (% of households with a female head) -SP.MTR.1519.ZS | Teenage mothers (% of women ages 15-19 who have had children or are currently pregnant) -SP.POP.0004.FE | Population ages 0-4, female -SP.POP.0004.FE.5Y | Population ages 0-4, female (% of female population) -SP.POP.0004.MA | Population ages 0-4, male -SP.POP.0004.MA.5Y | Population ages 0-4, male (% of male population) -SP.POP.0014.FE.ZS | Population ages 0-14, female (% of total) -SP.POP.0014.MA.ZS | Population ages 0-14, male (% of total) -SP.POP.0014.TO | Population ages 0-14, total -SP.POP.0014.TO.ZS | Population ages 0-14 (% of total) -SP.POP.0509.FE | Population ages 5-9, female -SP.POP.0509.FE.5Y | Population ages 5-9, female (% of female population) -SP.POP.0509.MA | Population ages 5-9, male -SP.POP.0509.MA.5Y | Population ages 5-9, male (% of male population) -SP.POP.1014.FE | Population ages 10-14, female -SP.POP.1014.FE.5Y | Population ages 10-14, female (% of female population) -SP.POP.1014.MA | Population ages 10-14, male -SP.POP.1014.MA.5Y | Population ages 10-14, male (% of male population) -SP.POP.1519.FE | Population ages 15-19, female -SP.POP.1519.FE.5Y | Population ages 15-19, female (% of female population) -SP.POP.1519.MA | Population ages 15-19, male -SP.POP.1519.MA.5Y | Population ages 15-19, male (% of male population) -SP.POP.1564.FE.ZS | Population ages 15-64, female (% of total) -SP.POP.1564.MA.ZS | Population ages 15-64, male (% of total) -SP.POP.1564.TO | Population ages 15-64, total -SP.POP.1564.TO.ZS | Population ages 15-64 (% of total) -SP.POP.2024.FE | Population ages 20-24, female -SP.POP.2024.FE.5Y | Population ages 20-24, female (% of female population) -SP.POP.2024.MA | Population ages 20-24, male -SP.POP.2024.MA.5Y | Population ages 20-24, male (% of male population) -SP.POP.2529.FE | Population ages 25-29, female -SP.POP.2529.FE.5Y | Population ages 25-29, female (% of female population) -SP.POP.2529.MA | Population ages 25-29, male -SP.POP.2529.MA.5Y | Population ages 25-29, male (% of male population) -SP.POP.3034.FE | Population ages 30-34, female -SP.POP.3034.FE.5Y | Population ages 30-34, female (% of female population) -SP.POP.3034.MA | Population ages 30-34, male -SP.POP.3034.MA.5Y | Population ages 30-34, male (% of male population) -SP.POP.3539.FE | Population ages 35-39, female -SP.POP.3539.FE.5Y | Population ages 35-39, female (% of female population) -SP.POP.3539.MA | Population ages 35-39, male -SP.POP.3539.MA.5Y | Population ages 35-39, male (% of male population) -SP.POP.4044.FE | Population ages 40-44, female -SP.POP.4044.FE.5Y | Population ages 40-44, female (% of female population) -SP.POP.4044.MA | Population ages 40-44, male -SP.POP.4044.MA.5Y | Population ages 40-44, male (% of male population) -SP.POP.4549.FE | Population ages 45-49, female -SP.POP.4549.FE.5Y | Population ages 45-49, female (% of female population) -SP.POP.4549.MA | Population ages 45-49, male -SP.POP.4549.MA.5Y | Population ages 45-49, male (% of male population) -SP.POP.5054.FE | Population ages 50-54, female -SP.POP.5054.FE.5Y | Population ages 50-54, female (% of female population) -SP.POP.5054.MA | Population ages 50-54, male -SP.POP.5054.MA.5Y | Population ages 50-54, male (% of male population) -SP.POP.5559.FE | Population ages 55-59, female -SP.POP.5559.FE.5Y | Population ages 55-59, female (% of female population) -SP.POP.5559.MA | Population ages 55-59, male -SP.POP.5559.MA.5Y | Population ages 55-59, male (% of male population) -SP.POP.6064.FE | Population ages 60-64, female -SP.POP.6064.FE.5Y | Population ages 60-64, female (% of female population) -SP.POP.6064.MA | Population ages 60-64, male -SP.POP.6064.MA.5Y | Population ages 60-64, male (% of male population) -SP.POP.6569.FE | Population ages 65-69, female -SP.POP.6569.FE.5Y | Population ages 65-69, female (% of female population) -SP.POP.6569.MA | Population ages 65-69, male -SP.POP.6569.MA.5Y | Population ages 65-69, male (% of male population) -SP.POP.65UP.FE.ZS | Population ages 65 and above, female (% of total) -SP.POP.65UP.MA.ZS | Population ages 65 and above, male (% of total) -SP.POP.65UP.TO | Population ages 65 and above, total -SP.POP.65UP.TO.ZS | Population ages 65 and above (% of total) -SP.POP.7074.FE | Population ages 70-74, female -SP.POP.7074.FE.5Y | Population ages 70-74, female (% of female population) -SP.POP.7074.MA | Population ages 70-74, male -SP.POP.7074.MA.5Y | Population ages 70-74, male (% of male population) -SP.POP.7579.FE | Population ages 75-79, female -SP.POP.7579.FE.5Y | Population ages 75-79, female (% of female population) -SP.POP.7579.MA | Population ages 75-79, male -SP.POP.7579.MA.5Y | Population ages 75-79, male (% of male population) -SP.POP.80UP.FE | Population ages 80 and above, female -SP.POP.80UP.FE.5Y | Population ages 80 and above, female (% of female population) -SP.POP.80UP.MA | Population ages 80 and above, male -SP.POP.80UP.MA.5Y | Population ages 80 and above, male (% of male population) -SP.POP.AG00.FE.IN | Age population, age 0, female, interpolated -SP.POP.AG00.MA.IN | Age population, age 0, male, interpolated -SP.POP.AG01.FE.IN | Age population, age 01, female, interpolated -SP.POP.AG01.MA.IN | Age population, age 01, male, interpolated -SP.POP.AG02.FE.IN | Age population, age 02, female, interpolated -SP.POP.AG02.MA.IN | Age population, age 02, male, interpolated -SP.POP.AG03.FE.IN | Age population, age 03, female, interpolated -SP.POP.AG03.MA.IN | Age population, age 03, male, interpolated -SP.POP.AG04.FE.IN | Age population, age 04, female, interpolated -SP.POP.AG04.MA.IN | Age population, age 04, male, interpolated -SP.POP.AG05.FE.IN | Age population, age 05, female, interpolated -SP.POP.AG05.MA.IN | Age population, age 05, male, interpolated -SP.POP.AG06.FE.IN | Age population, age 06, female, interpolated -SP.POP.AG06.MA.IN | Age population, age 06, male, interpolated -SP.POP.AG07.FE.IN | Age population, age 07, female, interpolated -SP.POP.AG07.MA.IN | Age population, age 07, male, interpolated -SP.POP.AG08.FE.IN | Age population, age 08, female, interpolated -SP.POP.AG08.MA.IN | Age population, age 08, male, interpolated -SP.POP.AG09.FE.IN | Age population, age 09, female, interpolated -SP.POP.AG09.MA.IN | Age population, age 09, male, interpolated -SP.POP.AG10.FE.IN | Age population, age 10, female, interpolated -SP.POP.AG10.MA.IN | Age population, age 10, male -SP.POP.AG11.FE.IN | Age population, age 11, female, interpolated -SP.POP.AG11.MA.IN | Age population, age 11, male -SP.POP.AG12.FE.IN | Age population, age 12, female, interpolated -SP.POP.AG12.MA.IN | Age population, age 12, male -SP.POP.AG13.FE.IN | Age population, age 13, female, interpolated -SP.POP.AG13.MA.IN | Age population, age 13, male -SP.POP.AG14.FE.IN | Age population, age 14, female, interpolated -SP.POP.AG14.MA.IN | Age population, age 14, male -SP.POP.AG15.FE.IN | Age population, age 15, female, interpolated -SP.POP.AG15.MA.IN | Age population, age 15, male, interpolated -SP.POP.AG16.FE.IN | Age population, age 16, female, interpolated -SP.POP.AG16.MA.IN | Age population, age 16, male, interpolated -SP.POP.AG17.FE.IN | Age population, age 17, female, interpolated -SP.POP.AG17.MA.IN | Age population, age 17, male, interpolated -SP.POP.AG18.FE.IN | Age population, age 18, female, interpolated -SP.POP.AG18.MA.IN | Age population, age 18, male, interpolated -SP.POP.AG19.FE.IN | Age population, age 19, female, interpolated -SP.POP.AG19.MA.IN | Age population, age 19, male, interpolated -SP.POP.AG20.FE.IN | Age population, age 20, female, interpolated -SP.POP.AG20.MA.IN | Age population, age 20, male, interpolated -SP.POP.AG21.FE.IN | Age population, age 21, female, interpolated -SP.POP.AG21.MA.IN | Age population, age 21, male, interpolated -SP.POP.AG22.FE.IN | Age population, age 22, female, interpolated -SP.POP.AG22.MA.IN | Age population, age 22, male, interpolated -SP.POP.AG23.FE.IN | Age population, age 23, female, interpolated -SP.POP.AG23.MA.IN | Age population, age 23, male, interpolated -SP.POP.AG24.FE.IN | Age population, age 24, female, interpolated -SP.POP.AG24.MA.IN | Age population, age 24, male, interpolated -SP.POP.AG25.FE.IN | Age population, age 25, female, interpolated -SP.POP.AG25.MA.IN | Age population, age 25, male, interpolated -SP.POP.BRTH.MF | Sex ratio at birth (male births per female births) -SP.POP.DPND | Age dependency ratio (% of working-age population) -SP.POP.DPND.OL | Age dependency ratio, old (% of working-age population) -SP.POP.DPND.YG | Age dependency ratio, young (% of working-age population) -SP.POP.GROW | Population growth (annual %) -SP.POP.TOTL | Population, total -SP.POP.TOTL.FE.IN | Population, female -SP.POP.TOTL.FE.ZS | Population, female (% of total) -SP.POP.TOTL.MA.IN | Population, male -SP.POP.TOTL.MA.ZS | Population, male (% of total) -SP.REG.BRTH.RU.ZS | Completeness of birth registration, rural (%) -SP.REG.BRTH.UR.ZS | Completeness of birth registration, urban (%) -SP.REG.BRTH.ZS | Completeness of birth registration (%) -SP.REG.DTHS.ZS | Completeness of death registration with cause-of-death information (%) -SP.RUR.TOTL | Rural population -SP.RUR.TOTL.ZG | Rural population growth (annual %) -SP.RUR.TOTL.ZS | Rural population (% of total population) -SP.URB.GROW | Urban population growth (annual %) -SP.URB.TOTL | Urban population -SP.URB.TOTL.IN.ZS | Urban population (% of total) -SP.UWT.TFRT | Unmet need for contraception (% of married women ages 15-49) diff --git a/panoramix/data/countries.py b/panoramix/data/countries.py deleted file mode 100644 index f81ef32df27f..000000000000 --- a/panoramix/data/countries.py +++ /dev/null @@ -1,2494 +0,0 @@ -""" -This module contains data related to countries and is used for geo mapping -""" - -countries = [ - { - "name": "Angola", - "area": 1246700, - "cioc": "ANG", - "cca2": "AO", - "capital": "Luanda", - "lat": -12.5, - "lng": 18.5, - "cca3": "AGO" - }, - { - "name": "Algeria", - "area": 2381741, - "cioc": "ALG", - "cca2": "DZ", - "capital": "Algiers", - "lat": 28, - "lng": 3, - "cca3": "DZA" - }, - { - "name": "Egypt", - "area": 1002450, - "cioc": "EGY", - "cca2": "EG", - "capital": "Cairo", - "lat": 27, - "lng": 30, - "cca3": "EGY" - }, - { - "name": "Bangladesh", - "area": 147570, - "cioc": "BAN", - "cca2": "BD", - "capital": "Dhaka", - "lat": 24, - "lng": 90, - "cca3": "BGD" - }, - { - "name": "Niger", - "area": 1267000, - "cioc": "NIG", - "cca2": "NE", - "capital": "Niamey", - "lat": 16, - "lng": 8, - "cca3": "NER" - }, - { - "name": "Liechtenstein", - "area": 160, - "cioc": "LIE", - "cca2": "LI", - "capital": "Vaduz", - "lat": 47.26666666, - "lng": 9.53333333, - "cca3": "LIE" - }, - { - "name": "Namibia", - "area": 825615, - "cioc": "NAM", - "cca2": "NA", - "capital": "Windhoek", - "lat": -22, - "lng": 17, - "cca3": "NAM" - }, - { - "name": "Bulgaria", - "area": 110879, - "cioc": "BUL", - "cca2": "BG", - "capital": "Sofia", - "lat": 43, - "lng": 25, - "cca3": "BGR" - }, - { - "name": "Bolivia", - "area": 1098581, - "cioc": "BOL", - "cca2": "BO", - "capital": "Sucre", - "lat": -17, - "lng": -65, - "cca3": "BOL" - }, - { - "name": "Ghana", - "area": 238533, - "cioc": "GHA", - "cca2": "GH", - "capital": "Accra", - "lat": 8, - "lng": -2, - "cca3": "GHA" - }, - { - "name": "Cocos (Keeling) Islands", - "area": 14, - "cioc": "", - "cca2": "CC", - "capital": "West Island", - "lat": -12.5, - "lng": 96.83333333, - "cca3": "CCK" - }, - { - "name": "Pakistan", - "area": 881912, - "cioc": "PAK", - "cca2": "PK", - "capital": "Islamabad", - "lat": 30, - "lng": 70, - "cca3": "PAK" - }, - { - "name": "Cape Verde", - "area": 4033, - "cioc": "CPV", - "cca2": "CV", - "capital": "Praia", - "lat": 16, - "lng": -24, - "cca3": "CPV" - }, - { - "name": "Jordan", - "area": 89342, - "cioc": "JOR", - "cca2": "JO", - "capital": "Amman", - "lat": 31, - "lng": 36, - "cca3": "JOR" - }, - { - "name": "Liberia", - "area": 111369, - "cioc": "LBR", - "cca2": "LR", - "capital": "Monrovia", - "lat": 6.5, - "lng": -9.5, - "cca3": "LBR" - }, - { - "name": "Libya", - "area": 1759540, - "cioc": "LBA", - "cca2": "LY", - "capital": "Tripoli", - "lat": 25, - "lng": 17, - "cca3": "LBY" - }, - { - "name": "Malaysia", - "area": 330803, - "cioc": "MAS", - "cca2": "MY", - "capital": "Kuala Lumpur", - "lat": 2.5, - "lng": 112.5, - "cca3": "MYS" - }, - { - "name": "Dominican Republic", - "area": 48671, - "cioc": "DOM", - "cca2": "DO", - "capital": "Santo Domingo", - "lat": 19, - "lng": -70.66666666, - "cca3": "DOM" - }, - { - "name": "Puerto Rico", - "area": 8870, - "cioc": "PUR", - "cca2": "PR", - "capital": "San Juan", - "lat": 18.25, - "lng": -66.5, - "cca3": "PRI" - }, - { - "name": "Mayotte", - "area": 374, - "cioc": "", - "cca2": "YT", - "capital": "Mamoudzou", - "lat": -12.83333333, - "lng": 45.16666666, - "cca3": "MYT" - }, - { - "name": "North Korea", - "area": 120538, - "cioc": "PRK", - "cca2": "KP", - "capital": "Pyongyang", - "lat": 40, - "lng": 127, - "cca3": "PRK" - }, - { - "name": "Palestine", - "area": 6220, - "cioc": "PLE", - "cca2": "PS", - "capital": "Ramallah", - "lat": 31.9, - "lng": 35.2, - "cca3": "PSE" - }, - { - "name": "Tanzania", - "area": 945087, - "cioc": "TAN", - "cca2": "TZ", - "capital": "Dodoma", - "lat": -6, - "lng": 35, - "cca3": "TZA" - }, - { - "name": "Botswana", - "area": 582000, - "cioc": "BOT", - "cca2": "BW", - "capital": "Gaborone", - "lat": -22, - "lng": 24, - "cca3": "BWA" - }, - { - "name": "Cambodia", - "area": 181035, - "cioc": "CAM", - "cca2": "KH", - "capital": "Phnom Penh", - "lat": 13, - "lng": 105, - "cca3": "KHM" - }, - { - "name": "Nicaragua", - "area": 130373, - "cioc": "NCA", - "cca2": "NI", - "capital": "Managua", - "lat": 13, - "lng": -85, - "cca3": "NIC" - }, - { - "name": "Trinidad and Tobago", - "area": 5130, - "cioc": "TTO", - "cca2": "TT", - "capital": "Port of Spain", - "lat": 11, - "lng": -61, - "cca3": "TTO" - }, - { - "name": "Ethiopia", - "area": 1104300, - "cioc": "ETH", - "cca2": "ET", - "capital": "Addis Ababa", - "lat": 8, - "lng": 38, - "cca3": "ETH" - }, - { - "name": "Paraguay", - "area": 406752, - "cioc": "PAR", - "cca2": "PY", - "capital": "Asuncion", - "lat": -23, - "lng": -58, - "cca3": "PRY" - }, - { - "name": "Hong Kong", - "area": 1104, - "cioc": "HKG", - "cca2": "HK", - "capital": "City of Victoria", - "lat": 22.267, - "lng": 114.188, - "cca3": "HKG" - }, - { - "name": "Saudi Arabia", - "area": 2149690, - "cioc": "KSA", - "cca2": "SA", - "capital": "Riyadh", - "lat": 25, - "lng": 45, - "cca3": "SAU" - }, - { - "name": "Lebanon", - "area": 10452, - "cioc": "LIB", - "cca2": "LB", - "capital": "Beirut", - "lat": 33.83333333, - "lng": 35.83333333, - "cca3": "LBN" - }, - { - "name": "Slovenia", - "area": 20273, - "cioc": "SLO", - "cca2": "SI", - "capital": "Ljubljana", - "lat": 46.11666666, - "lng": 14.81666666, - "cca3": "SVN" - }, - { - "name": "Burkina Faso", - "area": 272967, - "cioc": "BUR", - "cca2": "BF", - "capital": "Ouagadougou", - "lat": 13, - "lng": -2, - "cca3": "BFA" - }, - { - "name": "Switzerland", - "area": 41284, - "cioc": "SUI", - "cca2": "CH", - "capital": "Bern", - "lat": 47, - "lng": 8, - "cca3": "CHE" - }, - { - "name": "Mauritania", - "area": 1030700, - "cioc": "MTN", - "cca2": "MR", - "capital": "Nouakchott", - "lat": 20, - "lng": -12, - "cca3": "MRT" - }, - { - "name": "Croatia", - "area": 56594, - "cioc": "CRO", - "cca2": "HR", - "capital": "Zagreb", - "lat": 45.16666666, - "lng": 15.5, - "cca3": "HRV" - }, - { - "name": "Chile", - "area": 756102, - "cioc": "CHI", - "cca2": "CL", - "capital": "Santiago", - "lat": -30, - "lng": -71, - "cca3": "CHL" - }, - { - "name": "China", - "area": 9706961, - "cioc": "CHN", - "cca2": "CN", - "capital": "Beijing", - "lat": 35, - "lng": 105, - "cca3": "CHN" - }, - { - "name": "Saint Kitts and Nevis", - "area": 261, - "cioc": "SKN", - "cca2": "KN", - "capital": "Basseterre", - "lat": 17.33333333, - "lng": -62.75, - "cca3": "KNA" - }, - { - "name": "Sierra Leone", - "area": 71740, - "cioc": "SLE", - "cca2": "SL", - "capital": "Freetown", - "lat": 8.5, - "lng": -11.5, - "cca3": "SLE" - }, - { - "name": "Jamaica", - "area": 10991, - "cioc": "JAM", - "cca2": "JM", - "capital": "Kingston", - "lat": 18.25, - "lng": -77.5, - "cca3": "JAM" - }, - { - "name": "San Marino", - "area": 61, - "cioc": "SMR", - "cca2": "SM", - "capital": "City of San Marino", - "lat": 43.76666666, - "lng": 12.41666666, - "cca3": "SMR" - }, - { - "name": "Gibraltar", - "area": 6, - "cioc": "", - "cca2": "GI", - "capital": "Gibraltar", - "lat": 36.13333333, - "lng": -5.35, - "cca3": "GIB" - }, - { - "name": "Djibouti", - "area": 23200, - "cioc": "DJI", - "cca2": "DJ", - "capital": "Djibouti", - "lat": 11.5, - "lng": 43, - "cca3": "DJI" - }, - { - "name": "Guinea", - "area": 245857, - "cioc": "GUI", - "cca2": "GN", - "capital": "Conakry", - "lat": 11, - "lng": -10, - "cca3": "GIN" - }, - { - "name": "Finland", - "area": 338424, - "cioc": "FIN", - "cca2": "FI", - "capital": "Helsinki", - "lat": 64, - "lng": 26, - "cca3": "FIN" - }, - { - "name": "Uruguay", - "area": 181034, - "cioc": "URU", - "cca2": "UY", - "capital": "Montevideo", - "lat": -33, - "lng": -56, - "cca3": "URY" - }, - { - "name": "Thailand", - "area": 513120, - "cioc": "THA", - "cca2": "TH", - "capital": "Bangkok", - "lat": 15, - "lng": 100, - "cca3": "THA" - }, - { - "name": "Sao Tome and Principe", - "area": 964, - "cioc": "STP", - "cca2": "ST", - "capital": "Sao Tome", - "lat": 1, - "lng": 7, - "cca3": "STP" - }, - { - "name": "Seychelles", - "area": 452, - "cioc": "SEY", - "cca2": "SC", - "capital": "Victoria", - "lat": -4.58333333, - "lng": 55.66666666, - "cca3": "SYC" - }, - { - "name": "Nepal", - "area": 147181, - "cioc": "NEP", - "cca2": "NP", - "capital": "Kathmandu", - "lat": 28, - "lng": 84, - "cca3": "NPL" - }, - { - "name": "Christmas Island", - "area": 135, - "cioc": "", - "cca2": "CX", - "capital": "Flying Fish Cove", - "lat": -10.5, - "lng": 105.66666666, - "cca3": "CXR" - }, - { - "name": "Laos", - "area": 236800, - "cioc": "LAO", - "cca2": "LA", - "capital": "Vientiane", - "lat": 18, - "lng": 105, - "cca3": "LAO" - }, - { - "name": "Yemen", - "area": 527968, - "cioc": "YEM", - "cca2": "YE", - "capital": "Sana'a", - "lat": 15, - "lng": 48, - "cca3": "YEM" - }, - { - "name": "Bouvet Island", - "area": 49, - "cioc": "", - "cca2": "BV", - "capital": "", - "lat": -54.43333333, - "lng": 3.4, - "cca3": "BVT" - }, - { - "name": "South Africa", - "area": 1221037, - "cioc": "RSA", - "cca2": "ZA", - "capital": "Pretoria", - "lat": -29, - "lng": 24, - "cca3": "ZAF" - }, - { - "name": "Kiribati", - "area": 811, - "cioc": "KIR", - "cca2": "KI", - "capital": "South Tarawa", - "lat": 1.41666666, - "lng": 173, - "cca3": "KIR" - }, - { - "name": "Philippines", - "area": 342353, - "cioc": "PHI", - "cca2": "PH", - "capital": "Manila", - "lat": 13, - "lng": 122, - "cca3": "PHL" - }, - { - "name": "Sint Maarten", - "area": 34, - "cioc": "", - "cca2": "SX", - "capital": "Philipsburg", - "lat": 18.033333, - "lng": -63.05, - "cca3": "SXM" - }, - { - "name": "Romania", - "area": 238391, - "cioc": "ROU", - "cca2": "RO", - "capital": "Bucharest", - "lat": 46, - "lng": 25, - "cca3": "ROU" - }, - { - "name": "United States Virgin Islands", - "area": 347, - "cioc": "ISV", - "cca2": "VI", - "capital": "Charlotte Amalie", - "lat": 18.35, - "lng": -64.933333, - "cca3": "VIR" - }, - { - "name": "Syria", - "area": 185180, - "cioc": "SYR", - "cca2": "SY", - "capital": "Damascus", - "lat": 35, - "lng": 38, - "cca3": "SYR" - }, - { - "name": "Macau", - "area": 30, - "cioc": "", - "cca2": "MO", - "capital": "", - "lat": 22.16666666, - "lng": 113.55, - "cca3": "MAC" - }, - { - "name": "Saint Martin", - "area": 53, - "cioc": "", - "cca2": "MF", - "capital": "Marigot", - "lat": 18.08333333, - "lng": -63.95, - "cca3": "MAF" - }, - { - "name": "Malta", - "area": 316, - "cioc": "MLT", - "cca2": "MT", - "capital": "Valletta", - "lat": 35.83333333, - "lng": 14.58333333, - "cca3": "MLT" - }, - { - "name": "Kazakhstan", - "area": 2724900, - "cioc": "KAZ", - "cca2": "KZ", - "capital": "Astana", - "lat": 48, - "lng": 68, - "cca3": "KAZ" - }, - { - "name": "Turks and Caicos Islands", - "area": 948, - "cioc": "", - "cca2": "TC", - "capital": "Cockburn Town", - "lat": 21.75, - "lng": -71.58333333, - "cca3": "TCA" - }, - { - "name": "French Polynesia", - "area": 4167, - "cioc": "", - "cca2": "PF", - "capital": "Papeete", - "lat": -15, - "lng": -140, - "cca3": "PYF" - }, - { - "name": "Niue", - "area": 260, - "cioc": "", - "cca2": "NU", - "capital": "Alofi", - "lat": -19.03333333, - "lng": -169.86666666, - "cca3": "NIU" - }, - { - "name": "Dominica", - "area": 751, - "cioc": "DMA", - "cca2": "DM", - "capital": "Roseau", - "lat": 15.41666666, - "lng": -61.33333333, - "cca3": "DMA" - }, - { - "name": "Benin", - "area": 112622, - "cioc": "BEN", - "cca2": "BJ", - "capital": "Porto-Novo", - "lat": 9.5, - "lng": 2.25, - "cca3": "BEN" - }, - { - "name": "French Guiana", - "area": 83534, - "cioc": "", - "cca2": "GF", - "capital": "Cayenne", - "lat": 4, - "lng": -53, - "cca3": "GUF" - }, - { - "name": "Belgium", - "area": 30528, - "cioc": "BEL", - "cca2": "BE", - "capital": "Brussels", - "lat": 50.83333333, - "lng": 4, - "cca3": "BEL" - }, - { - "name": "Montserrat", - "area": 102, - "cioc": "", - "cca2": "MS", - "capital": "Plymouth", - "lat": 16.75, - "lng": -62.2, - "cca3": "MSR" - }, - { - "name": "Togo", - "area": 56785, - "cioc": "TOG", - "cca2": "TG", - "capital": "Lome", - "lat": 8, - "lng": 1.16666666, - "cca3": "TGO" - }, - { - "name": "Germany", - "area": 357114, - "cioc": "GER", - "cca2": "DE", - "capital": "Berlin", - "lat": 51, - "lng": 9, - "cca3": "DEU" - }, - { - "name": "Guam", - "area": 549, - "cioc": "GUM", - "cca2": "GU", - "capital": "Hagatna", - "lat": 13.46666666, - "lng": 144.78333333, - "cca3": "GUM" - }, - { - "name": "Sri Lanka", - "area": 65610, - "cioc": "SRI", - "cca2": "LK", - "capital": "Colombo", - "lat": 7, - "lng": 81, - "cca3": "LKA" - }, - { - "name": "South Sudan", - "area": 619745, - "cioc": "", - "cca2": "SS", - "capital": "Juba", - "lat": 7, - "lng": 30, - "cca3": "SSD" - }, - { - "name": "Falkland Islands", - "area": 12173, - "cioc": "", - "cca2": "FK", - "capital": "Stanley", - "lat": -51.75, - "lng": -59, - "cca3": "FLK" - }, - { - "name": "United Kingdom", - "area": 242900, - "cioc": "GBR", - "cca2": "GB", - "capital": "London", - "lat": 54, - "lng": -2, - "cca3": "GBR" - }, - { - "name": "Guyana", - "area": 214969, - "cioc": "GUY", - "cca2": "GY", - "capital": "Georgetown", - "lat": 5, - "lng": -59, - "cca3": "GUY" - }, - { - "name": "Costa Rica", - "area": 51100, - "cioc": "CRC", - "cca2": "CR", - "capital": "San Jose", - "lat": 10, - "lng": -84, - "cca3": "CRI" - }, - { - "name": "Cameroon", - "area": 475442, - "cioc": "CMR", - "cca2": "CM", - "capital": "Yaounde", - "lat": 6, - "lng": 12, - "cca3": "CMR" - }, - { - "name": "Morocco", - "area": 446550, - "cioc": "MAR", - "cca2": "MA", - "capital": "Rabat", - "lat": 32, - "lng": -5, - "cca3": "MAR" - }, - { - "name": "Northern Mariana Islands", - "area": 464, - "cioc": "", - "cca2": "MP", - "capital": "Saipan", - "lat": 15.2, - "lng": 145.75, - "cca3": "MNP" - }, - { - "name": "Lesotho", - "area": 30355, - "cioc": "LES", - "cca2": "LS", - "capital": "Maseru", - "lat": -29.5, - "lng": 28.5, - "cca3": "LSO" - }, - { - "name": "Hungary", - "area": 93028, - "cioc": "HUN", - "cca2": "HU", - "capital": "Budapest", - "lat": 47, - "lng": 20, - "cca3": "HUN" - }, - { - "name": "Turkmenistan", - "area": 488100, - "cioc": "TKM", - "cca2": "TM", - "capital": "Ashgabat", - "lat": 40, - "lng": 60, - "cca3": "TKM" - }, - { - "name": "Suriname", - "area": 163820, - "cioc": "SUR", - "cca2": "SR", - "capital": "Paramaribo", - "lat": 4, - "lng": -56, - "cca3": "SUR" - }, - { - "name": "Netherlands", - "area": 41850, - "cioc": "NED", - "cca2": "NL", - "capital": "Amsterdam", - "lat": 52.5, - "lng": 5.75, - "cca3": "NLD" - }, - { - "name": "Bermuda", - "area": 54, - "cioc": "BER", - "cca2": "BM", - "capital": "Hamilton", - "lat": 32.33333333, - "lng": -64.75, - "cca3": "BMU" - }, - { - "name": "Heard Island and McDonald Islands", - "area": 412, - "cioc": "", - "cca2": "HM", - "capital": "", - "lat": -53.1, - "lng": 72.51666666, - "cca3": "HMD" - }, - { - "name": "Chad", - "area": 1284000, - "cioc": "CHA", - "cca2": "TD", - "capital": "N'Djamena", - "lat": 15, - "lng": 19, - "cca3": "TCD" - }, - { - "name": "Georgia", - "area": 69700, - "cioc": "GEO", - "cca2": "GE", - "capital": "Tbilisi", - "lat": 42, - "lng": 43.5, - "cca3": "GEO" - }, - { - "name": "Montenegro", - "area": 13812, - "cioc": "MNE", - "cca2": "ME", - "capital": "Podgorica", - "lat": 42.5, - "lng": 19.3, - "cca3": "MNE" - }, - { - "name": "Mongolia", - "area": 1564110, - "cioc": "MGL", - "cca2": "MN", - "capital": "Ulan Bator", - "lat": 46, - "lng": 105, - "cca3": "MNG" - }, - { - "name": "Marshall Islands", - "area": 181, - "cioc": "MHL", - "cca2": "MH", - "capital": "Majuro", - "lat": 9, - "lng": 168, - "cca3": "MHL" - }, - { - "name": "Martinique", - "area": 1128, - "cioc": "", - "cca2": "MQ", - "capital": "Fort-de-France", - "lat": 14.666667, - "lng": -61, - "cca3": "MTQ" - }, - { - "name": "Belize", - "area": 22966, - "cioc": "BIZ", - "cca2": "BZ", - "capital": "Belmopan", - "lat": 17.25, - "lng": -88.75, - "cca3": "BLZ" - }, - { - "name": "Norfolk Island", - "area": 36, - "cioc": "", - "cca2": "NF", - "capital": "Kingston", - "lat": -29.03333333, - "lng": 167.95, - "cca3": "NFK" - }, - { - "name": "Myanmar", - "area": 676578, - "cioc": "MYA", - "cca2": "MM", - "capital": "Naypyidaw", - "lat": 22, - "lng": 98, - "cca3": "MMR" - }, - { - "name": "Afghanistan", - "area": 652230, - "cioc": "AFG", - "cca2": "AF", - "capital": "Kabul", - "lat": 33, - "lng": 65, - "cca3": "AFG" - }, - { - "name": "Burundi", - "area": 27834, - "cioc": "BDI", - "cca2": "BI", - "capital": "Bujumbura", - "lat": -3.5, - "lng": 30, - "cca3": "BDI" - }, - { - "name": "British Virgin Islands", - "area": 151, - "cioc": "IVB", - "cca2": "VG", - "capital": "Road Town", - "lat": 18.431383, - "lng": -64.62305, - "cca3": "VGB" - }, - { - "name": "Belarus", - "area": 207600, - "cioc": "BLR", - "cca2": "BY", - "capital": "Minsk", - "lat": 53, - "lng": 28, - "cca3": "BLR" - }, - { - "name": "Saint Barthelemy", - "area": 21, - "cioc": "", - "cca2": "BL", - "capital": "Gustavia", - "lat": 18.5, - "lng": -63.41666666, - "cca3": "BLM" - }, - { - "name": "Grenada", - "area": 344, - "cioc": "GRN", - "cca2": "GD", - "capital": "St. George's", - "lat": 12.11666666, - "lng": -61.66666666, - "cca3": "GRD" - }, - { - "name": "Tokelau", - "area": 12, - "cioc": "", - "cca2": "TK", - "capital": "Fakaofo", - "lat": -9, - "lng": -172, - "cca3": "TKL" - }, - { - "name": "Greece", - "area": 131990, - "cioc": "GRE", - "cca2": "GR", - "capital": "Athens", - "lat": 39, - "lng": 22, - "cca3": "GRC" - }, - { - "name": "Russia", - "area": 17098242, - "cioc": "RUS", - "cca2": "RU", - "capital": "Moscow", - "lat": 60, - "lng": 100, - "cca3": "RUS" - }, - { - "name": "Greenland", - "area": 2166086, - "cioc": "", - "cca2": "GL", - "capital": "Nuuk", - "lat": 72, - "lng": -40, - "cca3": "GRL" - }, - { - "name": "Andorra", - "area": 468, - "cioc": "AND", - "cca2": "AD", - "capital": "Andorra la Vella", - "lat": 42.5, - "lng": 1.5, - "cca3": "AND" - }, - { - "name": "Mozambique", - "area": 801590, - "cioc": "MOZ", - "cca2": "MZ", - "capital": "Maputo", - "lat": -18.25, - "lng": 35, - "cca3": "MOZ" - }, - { - "name": "Tajikistan", - "area": 143100, - "cioc": "TJK", - "cca2": "TJ", - "capital": "Dushanbe", - "lat": 39, - "lng": 71, - "cca3": "TJK" - }, - { - "name": "Haiti", - "area": 27750, - "cioc": "HAI", - "cca2": "HT", - "capital": "Port-au-Prince", - "lat": 19, - "lng": -72.41666666, - "cca3": "HTI" - }, - { - "name": "Mexico", - "area": 1964375, - "cioc": "MEX", - "cca2": "MX", - "capital": "Mexico City", - "lat": 23, - "lng": -102, - "cca3": "MEX" - }, - { - "name": "Zimbabwe", - "area": 390757, - "cioc": "ZIM", - "cca2": "ZW", - "capital": "Harare", - "lat": -20, - "lng": 30, - "cca3": "ZWE" - }, - { - "name": "Saint Lucia", - "area": 616, - "cioc": "LCA", - "cca2": "LC", - "capital": "Castries", - "lat": 13.88333333, - "lng": -60.96666666, - "cca3": "LCA" - }, - { - "name": "India", - "area": 3287590, - "cioc": "IND", - "cca2": "IN", - "capital": "New Delhi", - "lat": 20, - "lng": 77, - "cca3": "IND" - }, - { - "name": "Latvia", - "area": 64559, - "cioc": "LAT", - "cca2": "LV", - "capital": "Riga", - "lat": 57, - "lng": 25, - "cca3": "LVA" - }, - { - "name": "Bhutan", - "area": 38394, - "cioc": "BHU", - "cca2": "BT", - "capital": "Thimphu", - "lat": 27.5, - "lng": 90.5, - "cca3": "BTN" - }, - { - "name": "Saint Vincent and the Grenadines", - "area": 389, - "cioc": "VIN", - "cca2": "VC", - "capital": "Kingstown", - "lat": 13.25, - "lng": -61.2, - "cca3": "VCT" - }, - { - "name": "Vietnam", - "area": 331212, - "cioc": "VIE", - "cca2": "VN", - "capital": "Hanoi", - "lat": 16.16666666, - "lng": 107.83333333, - "cca3": "VNM" - }, - { - "name": "Norway", - "area": 323802, - "cioc": "NOR", - "cca2": "NO", - "capital": "Oslo", - "lat": 62, - "lng": 10, - "cca3": "NOR" - }, - { - "name": "Czech Republic", - "area": 78865, - "cioc": "CZE", - "cca2": "CZ", - "capital": "Prague", - "lat": 49.75, - "lng": 15.5, - "cca3": "CZE" - }, - { - "name": "French Southern and Antarctic Lands", - "area": 7747, - "cioc": "", - "cca2": "TF", - "capital": "Port-aux-Francais", - "lat": -49.25, - "lng": 69.167, - "cca3": "ATF" - }, - { - "name": "Antigua and Barbuda", - "area": 442, - "cioc": "ANT", - "cca2": "AG", - "capital": "Saint John's", - "lat": 17.05, - "lng": -61.8, - "cca3": "ATG" - }, - { - "name": "Fiji", - "area": 18272, - "cioc": "FIJ", - "cca2": "FJ", - "capital": "Suva", - "lat": -18, - "lng": 175, - "cca3": "FJI" - }, - { - "name": "British Indian Ocean Territory", - "area": 60, - "cioc": "", - "cca2": "IO", - "capital": "Diego Garcia", - "lat": -6, - "lng": 71.5, - "cca3": "IOT" - }, - { - "name": "Honduras", - "area": 112492, - "cioc": "HON", - "cca2": "HN", - "capital": "Tegucigalpa", - "lat": 15, - "lng": -86.5, - "cca3": "HND" - }, - { - "name": "Mauritius", - "area": 2040, - "cioc": "MRI", - "cca2": "MU", - "capital": "Port Louis", - "lat": -20.28333333, - "lng": 57.55, - "cca3": "MUS" - }, - { - "name": "Antarctica", - "area": 14000000, - "cioc": "", - "cca2": "AQ", - "capital": "", - "lat": -90, - "lng": 0, - "cca3": "ATA" - }, - { - "name": "Luxembourg", - "area": 2586, - "cioc": "LUX", - "cca2": "LU", - "capital": "Luxembourg", - "lat": 49.75, - "lng": 6.16666666, - "cca3": "LUX" - }, - { - "name": "Israel", - "area": 20770, - "cioc": "ISR", - "cca2": "IL", - "capital": "Jerusalem", - "lat": 31.47, - "lng": 35.13, - "cca3": "ISR" - }, - { - "name": "Micronesia", - "area": 702, - "cioc": "FSM", - "cca2": "FM", - "capital": "Palikir", - "lat": 6.91666666, - "lng": 158.25, - "cca3": "FSM" - }, - { - "name": "Peru", - "area": 1285216, - "cioc": "PER", - "cca2": "PE", - "capital": "Lima", - "lat": -10, - "lng": -76, - "cca3": "PER" - }, - { - "name": "Reunion", - "area": 2511, - "cioc": "", - "cca2": "RE", - "capital": "Saint-Denis", - "lat": -21.15, - "lng": 55.5, - "cca3": "REU" - }, - { - "name": "Indonesia", - "area": 1904569, - "cioc": "INA", - "cca2": "ID", - "capital": "Jakarta", - "lat": -5, - "lng": 120, - "cca3": "IDN" - }, - { - "name": "Vanuatu", - "area": 12189, - "cioc": "VAN", - "cca2": "VU", - "capital": "Port Vila", - "lat": -16, - "lng": 167, - "cca3": "VUT" - }, - { - "name": "Macedonia", - "area": 25713, - "cioc": "MKD", - "cca2": "MK", - "capital": "Skopje", - "lat": 41.83333333, - "lng": 22, - "cca3": "MKD" - }, - { - "name": "DR Congo", - "area": 2344858, - "cioc": "COD", - "cca2": "CD", - "capital": "Kinshasa", - "lat": 0, - "lng": 25, - "cca3": "COD" - }, - { - "name": "Republic of the Congo", - "area": 342000, - "cioc": "CGO", - "cca2": "CG", - "capital": "Brazzaville", - "lat": -1, - "lng": 15, - "cca3": "COG" - }, - { - "name": "Iceland", - "area": 103000, - "cioc": "ISL", - "cca2": "IS", - "capital": "Reykjavik", - "lat": 65, - "lng": -18, - "cca3": "ISL" - }, - { - "name": "Guadeloupe", - "area": 1628, - "cioc": "", - "cca2": "GP", - "capital": "Basse-Terre", - "lat": 16.25, - "lng": -61.583333, - "cca3": "GLP" - }, - { - "name": "Cook Islands", - "area": 236, - "cioc": "COK", - "cca2": "CK", - "capital": "Avarua", - "lat": -21.23333333, - "lng": -159.76666666, - "cca3": "COK" - }, - { - "name": "Comoros", - "area": 1862, - "cioc": "COM", - "cca2": "KM", - "capital": "Moroni", - "lat": -12.16666666, - "lng": 44.25, - "cca3": "COM" - }, - { - "name": "Colombia", - "area": 1141748, - "cioc": "COL", - "cca2": "CO", - "capital": "Bogota", - "lat": 4, - "lng": -72, - "cca3": "COL" - }, - { - "name": "Nigeria", - "area": 923768, - "cioc": "NGR", - "cca2": "NG", - "capital": "Abuja", - "lat": 10, - "lng": 8, - "cca3": "NGA" - }, - { - "name": "Timor-Leste", - "area": 14874, - "cioc": "TLS", - "cca2": "TL", - "capital": "Dili", - "lat": -8.83333333, - "lng": 125.91666666, - "cca3": "TLS" - }, - { - "name": "Taiwan", - "area": 36193, - "cioc": "TPE", - "cca2": "TW", - "capital": "Taipei", - "lat": 23.5, - "lng": 121, - "cca3": "TWN" - }, - { - "name": "Portugal", - "area": 92090, - "cioc": "POR", - "cca2": "PT", - "capital": "Lisbon", - "lat": 39.5, - "lng": -8, - "cca3": "PRT" - }, - { - "name": "Moldova", - "area": 33846, - "cioc": "MDA", - "cca2": "MD", - "capital": "Chisinau", - "lat": 47, - "lng": 29, - "cca3": "MDA" - }, - { - "name": "Guernsey", - "area": 78, - "cioc": "", - "cca2": "GG", - "capital": "St. Peter Port", - "lat": 49.46666666, - "lng": -2.58333333, - "cca3": "GGY" - }, - { - "name": "Madagascar", - "area": 587041, - "cioc": "MAD", - "cca2": "MG", - "capital": "Antananarivo", - "lat": -20, - "lng": 47, - "cca3": "MDG" - }, - { - "name": "Ecuador", - "area": 276841, - "cioc": "ECU", - "cca2": "EC", - "capital": "Quito", - "lat": -2, - "lng": -77.5, - "cca3": "ECU" - }, - { - "name": "Senegal", - "area": 196722, - "cioc": "SEN", - "cca2": "SN", - "capital": "Dakar", - "lat": 14, - "lng": -14, - "cca3": "SEN" - }, - { - "name": "New Zealand", - "area": 270467, - "cioc": "NZL", - "cca2": "NZ", - "capital": "Wellington", - "lat": -41, - "lng": 174, - "cca3": "NZL" - }, - { - "name": "Maldives", - "area": 300, - "cioc": "MDV", - "cca2": "MV", - "capital": "Male", - "lat": 3.25, - "lng": 73, - "cca3": "MDV" - }, - { - "name": "American Samoa", - "area": 199, - "cioc": "ASA", - "cca2": "AS", - "capital": "Pago Pago", - "lat": -14.33333333, - "lng": -170, - "cca3": "ASM" - }, - { - "name": "Saint Pierre and Miquelon", - "area": 242, - "cioc": "", - "cca2": "PM", - "capital": "Saint-Pierre", - "lat": 46.83333333, - "lng": -56.33333333, - "cca3": "SPM" - }, - { - "name": "Curacao", - "area": 444, - "cioc": "", - "cca2": "CW", - "capital": "Willemstad", - "lat": 12.116667, - "lng": -68.933333, - "cca3": "CUW" - }, - { - "name": "France", - "area": 551695, - "cioc": "FRA", - "cca2": "FR", - "capital": "Paris", - "lat": 46, - "lng": 2, - "cca3": "FRA" - }, - { - "name": "Lithuania", - "area": 65300, - "cioc": "LTU", - "cca2": "LT", - "capital": "Vilnius", - "lat": 56, - "lng": 24, - "cca3": "LTU" - }, - { - "name": "Rwanda", - "area": 26338, - "cioc": "RWA", - "cca2": "RW", - "capital": "Kigali", - "lat": -2, - "lng": 30, - "cca3": "RWA" - }, - { - "name": "Zambia", - "area": 752612, - "cioc": "ZAM", - "cca2": "ZM", - "capital": "Lusaka", - "lat": -15, - "lng": 30, - "cca3": "ZMB" - }, - { - "name": "Gambia", - "area": 10689, - "cioc": "GAM", - "cca2": "GM", - "capital": "Banjul", - "lat": 13.46666666, - "lng": -16.56666666, - "cca3": "GMB" - }, - { - "name": "Wallis and Futuna", - "area": 142, - "cioc": "", - "cca2": "WF", - "capital": "Mata-Utu", - "lat": -13.3, - "lng": -176.2, - "cca3": "WLF" - }, - { - "name": "Jersey", - "area": 116, - "cioc": "", - "cca2": "JE", - "capital": "Saint Helier", - "lat": 49.25, - "lng": -2.16666666, - "cca3": "JEY" - }, - { - "name": "Faroe Islands", - "area": 1393, - "cioc": "", - "cca2": "FO", - "capital": "Torshavn", - "lat": 62, - "lng": -7, - "cca3": "FRO" - }, - { - "name": "Guatemala", - "area": 108889, - "cioc": "GUA", - "cca2": "GT", - "capital": "Guatemala City", - "lat": 15.5, - "lng": -90.25, - "cca3": "GTM" - }, - { - "name": "Denmark", - "area": 43094, - "cioc": "DEN", - "cca2": "DK", - "capital": "Copenhagen", - "lat": 56, - "lng": 10, - "cca3": "DNK" - }, - { - "name": "Isle of Man", - "area": 572, - "cioc": "", - "cca2": "IM", - "capital": "Douglas", - "lat": 54.25, - "lng": -4.5, - "cca3": "IMN" - }, - { - "name": "Australia", - "area": 7692024, - "cioc": "AUS", - "cca2": "AU", - "capital": "Canberra", - "lat": -27, - "lng": 133, - "cca3": "AUS" - }, - { - "name": "Austria", - "area": 83871, - "cioc": "AUT", - "cca2": "AT", - "capital": "Vienna", - "lat": 47.33333333, - "lng": 13.33333333, - "cca3": "AUT" - }, - { - "name": "Svalbard and Jan Mayen", - "area": -1, - "cioc": "", - "cca2": "SJ", - "capital": "Longyearbyen", - "lat": 78, - "lng": 20, - "cca3": "SJM" - }, - { - "name": "Venezuela", - "area": 916445, - "cioc": "VEN", - "cca2": "VE", - "capital": "Caracas", - "lat": 8, - "lng": -66, - "cca3": "VEN" - }, - { - "name": "Kosovo", - "area": 10908, - "cioc": "KOS", - "cca2": "XK", - "capital": "Pristina", - "lat": 42.666667, - "lng": 21.166667, - "cca3": "UNK" - }, - { - "name": "Palau", - "area": 459, - "cioc": "PLW", - "cca2": "PW", - "capital": "Ngerulmud", - "lat": 7.5, - "lng": 134.5, - "cca3": "PLW" - }, - { - "name": "Kenya", - "area": 580367, - "cioc": "KEN", - "cca2": "KE", - "capital": "Nairobi", - "lat": 1, - "lng": 38, - "cca3": "KEN" - }, - { - "name": "Samoa", - "area": 2842, - "cioc": "SAM", - "cca2": "WS", - "capital": "Apia", - "lat": -13.58333333, - "lng": -172.33333333, - "cca3": "WSM" - }, - { - "name": "Turkey", - "area": 783562, - "cioc": "TUR", - "cca2": "TR", - "capital": "Ankara", - "lat": 39, - "lng": 35, - "cca3": "TUR" - }, - { - "name": "Albania", - "area": 28748, - "cioc": "ALB", - "cca2": "AL", - "capital": "Tirana", - "lat": 41, - "lng": 20, - "cca3": "ALB" - }, - { - "name": "Oman", - "area": 309500, - "cioc": "OMA", - "cca2": "OM", - "capital": "Muscat", - "lat": 21, - "lng": 57, - "cca3": "OMN" - }, - { - "name": "Tuvalu", - "area": 26, - "cioc": "TUV", - "cca2": "TV", - "capital": "Funafuti", - "lat": -8, - "lng": 178, - "cca3": "TUV" - }, - { - "name": "Aland Islands", - "area": 1580, - "cioc": "", - "cca2": "AX", - "capital": "Mariehamn", - "lat": 60.116667, - "lng": 19.9, - "cca3": "ALA" - }, - { - "name": "Brunei", - "area": 5765, - "cioc": "BRU", - "cca2": "BN", - "capital": "Bandar Seri Begawan", - "lat": 4.5, - "lng": 114.66666666, - "cca3": "BRN" - }, - { - "name": "Tunisia", - "area": 163610, - "cioc": "TUN", - "cca2": "TN", - "capital": "Tunis", - "lat": 34, - "lng": 9, - "cca3": "TUN" - }, - { - "name": "Pitcairn Islands", - "area": 47, - "cioc": "", - "cca2": "PN", - "capital": "Adamstown", - "lat": -25.06666666, - "lng": -130.1, - "cca3": "PCN" - }, - { - "name": "Barbados", - "area": 430, - "cioc": "BAR", - "cca2": "BB", - "capital": "Bridgetown", - "lat": 13.16666666, - "lng": -59.53333333, - "cca3": "BRB" - }, - { - "name": "Brazil", - "area": 8515767, - "cioc": "BRA", - "cca2": "BR", - "capital": "Brasilia", - "lat": -10, - "lng": -55, - "cca3": "BRA" - }, - { - "name": "Ivory Coast", - "area": 322463, - "cioc": "CIV", - "cca2": "CI", - "capital": "Yamoussoukro", - "lat": 8, - "lng": -5, - "cca3": "CIV" - }, - { - "name": "Serbia", - "area": 88361, - "cioc": "SRB", - "cca2": "RS", - "capital": "Belgrade", - "lat": 44, - "lng": 21, - "cca3": "SRB" - }, - { - "name": "Equatorial Guinea", - "area": 28051, - "cioc": "GEQ", - "cca2": "GQ", - "capital": "Malabo", - "lat": 2, - "lng": 10, - "cca3": "GNQ" - }, - { - "name": "United States", - "area": 9372610, - "cioc": "USA", - "cca2": "US", - "capital": "Washington D.C.", - "lat": 38, - "lng": -97, - "cca3": "USA" - }, - { - "name": "Qatar", - "area": 11586, - "cioc": "QAT", - "cca2": "QA", - "capital": "Doha", - "lat": 25.5, - "lng": 51.25, - "cca3": "QAT" - }, - { - "name": "Sweden", - "area": 450295, - "cioc": "SWE", - "cca2": "SE", - "capital": "Stockholm", - "lat": 62, - "lng": 15, - "cca3": "SWE" - }, - { - "name": "Azerbaijan", - "area": 86600, - "cioc": "AZE", - "cca2": "AZ", - "capital": "Baku", - "lat": 40.5, - "lng": 47.5, - "cca3": "AZE" - }, - { - "name": "Guinea-Bissau", - "area": 36125, - "cioc": "GBS", - "cca2": "GW", - "capital": "Bissau", - "lat": 12, - "lng": -15, - "cca3": "GNB" - }, - { - "name": "Swaziland", - "area": 17364, - "cioc": "SWZ", - "cca2": "SZ", - "capital": "Lobamba", - "lat": -26.5, - "lng": 31.5, - "cca3": "SWZ" - }, - { - "name": "Tonga", - "area": 747, - "cioc": "TGA", - "cca2": "TO", - "capital": "Nuku'alofa", - "lat": -20, - "lng": -175, - "cca3": "TON" - }, - { - "name": "Canada", - "area": 9984670, - "cioc": "CAN", - "cca2": "CA", - "capital": "Ottawa", - "lat": 60, - "lng": -95, - "cca3": "CAN" - }, - { - "name": "Ukraine", - "area": 603500, - "cioc": "UKR", - "cca2": "UA", - "capital": "Kiev", - "lat": 49, - "lng": 32, - "cca3": "UKR" - }, - { - "name": "South Korea", - "area": 100210, - "cioc": "KOR", - "cca2": "KR", - "capital": "Seoul", - "lat": 37, - "lng": 127.5, - "cca3": "KOR" - }, - { - "name": "Anguilla", - "area": 91, - "cioc": "", - "cca2": "AI", - "capital": "The Valley", - "lat": 18.25, - "lng": -63.16666666, - "cca3": "AIA" - }, - { - "name": "Central African Republic", - "area": 622984, - "cioc": "CAF", - "cca2": "CF", - "capital": "Bangui", - "lat": 7, - "lng": 21, - "cca3": "CAF" - }, - { - "name": "Slovakia", - "area": 49037, - "cioc": "SVK", - "cca2": "SK", - "capital": "Bratislava", - "lat": 48.66666666, - "lng": 19.5, - "cca3": "SVK" - }, - { - "name": "Cyprus", - "area": 9251, - "cioc": "CYP", - "cca2": "CY", - "capital": "Nicosia", - "lat": 35, - "lng": 33, - "cca3": "CYP" - }, - { - "name": "Bosnia and Herzegovina", - "area": 51209, - "cioc": "BIH", - "cca2": "BA", - "capital": "Sarajevo", - "lat": 44, - "lng": 18, - "cca3": "BIH" - }, - { - "name": "Singapore", - "area": 710, - "cioc": "SIN", - "cca2": "SG", - "capital": "Singapore", - "lat": 1.36666666, - "lng": 103.8, - "cca3": "SGP" - }, - { - "name": "South Georgia", - "area": 3903, - "cioc": "", - "cca2": "GS", - "capital": "King Edward Point", - "lat": -54.5, - "lng": -37, - "cca3": "SGS" - }, - { - "name": "Somalia", - "area": 637657, - "cioc": "SOM", - "cca2": "SO", - "capital": "Mogadishu", - "lat": 10, - "lng": 49, - "cca3": "SOM" - }, - { - "name": "Uzbekistan", - "area": 447400, - "cioc": "UZB", - "cca2": "UZ", - "capital": "Tashkent", - "lat": 41, - "lng": 64, - "cca3": "UZB" - }, - { - "name": "Eritrea", - "area": 117600, - "cioc": "ERI", - "cca2": "ER", - "capital": "Asmara", - "lat": 15, - "lng": 39, - "cca3": "ERI" - }, - { - "name": "Poland", - "area": 312679, - "cioc": "POL", - "cca2": "PL", - "capital": "Warsaw", - "lat": 52, - "lng": 20, - "cca3": "POL" - }, - { - "name": "Kuwait", - "area": 17818, - "cioc": "KUW", - "cca2": "KW", - "capital": "Kuwait City", - "lat": 29.5, - "lng": 45.75, - "cca3": "KWT" - }, - { - "name": "Gabon", - "area": 267668, - "cioc": "GAB", - "cca2": "GA", - "capital": "Libreville", - "lat": -1, - "lng": 11.75, - "cca3": "GAB" - }, - { - "name": "Cayman Islands", - "area": 264, - "cioc": "CAY", - "cca2": "KY", - "capital": "George Town", - "lat": 19.5, - "lng": -80.5, - "cca3": "CYM" - }, - { - "name": "Vatican City", - "area": 0.44, - "cioc": "", - "cca2": "VA", - "capital": "Vatican City", - "lat": 41.9, - "lng": 12.45, - "cca3": "VAT" - }, - { - "name": "Estonia", - "area": 45227, - "cioc": "EST", - "cca2": "EE", - "capital": "Tallinn", - "lat": 59, - "lng": 26, - "cca3": "EST" - }, - { - "name": "Malawi", - "area": 118484, - "cioc": "MAW", - "cca2": "MW", - "capital": "Lilongwe", - "lat": -13.5, - "lng": 34, - "cca3": "MWI" - }, - { - "name": "Spain", - "area": 505992, - "cioc": "ESP", - "cca2": "ES", - "capital": "Madrid", - "lat": 40, - "lng": -4, - "cca3": "ESP" - }, - { - "name": "Iraq", - "area": 438317, - "cioc": "IRQ", - "cca2": "IQ", - "capital": "Baghdad", - "lat": 33, - "lng": 44, - "cca3": "IRQ" - }, - { - "name": "El Salvador", - "area": 21041, - "cioc": "ESA", - "cca2": "SV", - "capital": "San Salvador", - "lat": 13.83333333, - "lng": -88.91666666, - "cca3": "SLV" - }, - { - "name": "Mali", - "area": 1240192, - "cioc": "MLI", - "cca2": "ML", - "capital": "Bamako", - "lat": 17, - "lng": -4, - "cca3": "MLI" - }, - { - "name": "Ireland", - "area": 70273, - "cioc": "IRL", - "cca2": "IE", - "capital": "Dublin", - "lat": 53, - "lng": -8, - "cca3": "IRL" - }, - { - "name": "Iran", - "area": 1648195, - "cioc": "IRI", - "cca2": "IR", - "capital": "Tehran", - "lat": 32, - "lng": 53, - "cca3": "IRN" - }, - { - "name": "Aruba", - "area": 180, - "cioc": "ARU", - "cca2": "AW", - "capital": "Oranjestad", - "lat": 12.5, - "lng": -69.96666666, - "cca3": "ABW" - }, - { - "name": "Papua New Guinea", - "area": 462840, - "cioc": "PNG", - "cca2": "PG", - "capital": "Port Moresby", - "lat": -6, - "lng": 147, - "cca3": "PNG" - }, - { - "name": "Panama", - "area": 75417, - "cioc": "PAN", - "cca2": "PA", - "capital": "Panama City", - "lat": 9, - "lng": -80, - "cca3": "PAN" - }, - { - "name": "Sudan", - "area": 1886068, - "cioc": "SUD", - "cca2": "SD", - "capital": "Khartoum", - "lat": 15, - "lng": 30, - "cca3": "SDN" - }, - { - "name": "Solomon Islands", - "area": 28896, - "cioc": "SOL", - "cca2": "SB", - "capital": "Honiara", - "lat": -8, - "lng": 159, - "cca3": "SLB" - }, - { - "name": "Western Sahara", - "area": 266000, - "cioc": "", - "cca2": "EH", - "capital": "El Aaiun", - "lat": 24.5, - "lng": -13, - "cca3": "ESH" - }, - { - "name": "Monaco", - "area": 2.02, - "cioc": "MON", - "cca2": "MC", - "capital": "Monaco", - "lat": 43.73333333, - "lng": 7.4, - "cca3": "MCO" - }, - { - "name": "Italy", - "area": 301336, - "cioc": "ITA", - "cca2": "IT", - "capital": "Rome", - "lat": 42.83333333, - "lng": 12.83333333, - "cca3": "ITA" - }, - { - "name": "Japan", - "area": 377930, - "cioc": "JPN", - "cca2": "JP", - "capital": "Tokyo", - "lat": 36, - "lng": 138, - "cca3": "JPN" - }, - { - "name": "Kyrgyzstan", - "area": 199951, - "cioc": "KGZ", - "cca2": "KG", - "capital": "Bishkek", - "lat": 41, - "lng": 75, - "cca3": "KGZ" - }, - { - "name": "Uganda", - "area": 241550, - "cioc": "UGA", - "cca2": "UG", - "capital": "Kampala", - "lat": 1, - "lng": 32, - "cca3": "UGA" - }, - { - "name": "New Caledonia", - "area": 18575, - "cioc": "", - "cca2": "NC", - "capital": "Noumea", - "lat": -21.5, - "lng": 165.5, - "cca3": "NCL" - }, - { - "name": "United Arab Emirates", - "area": 83600, - "cioc": "UAE", - "cca2": "AE", - "capital": "Abu Dhabi", - "lat": 24, - "lng": 54, - "cca3": "ARE" - }, - { - "name": "Argentina", - "area": 2780400, - "cioc": "ARG", - "cca2": "AR", - "capital": "Buenos Aires", - "lat": -34, - "lng": -64, - "cca3": "ARG" - }, - { - "name": "Bahamas", - "area": 13943, - "cioc": "BAH", - "cca2": "BS", - "capital": "Nassau", - "lat": 24.25, - "lng": -76, - "cca3": "BHS" - }, - { - "name": "Bahrain", - "area": 765, - "cioc": "BRN", - "cca2": "BH", - "capital": "Manama", - "lat": 26, - "lng": 50.55, - "cca3": "BHR" - }, - { - "name": "Armenia", - "area": 29743, - "cioc": "ARM", - "cca2": "AM", - "capital": "Yerevan", - "lat": 40, - "lng": 45, - "cca3": "ARM" - }, - { - "name": "Nauru", - "area": 21, - "cioc": "NRU", - "cca2": "NR", - "capital": "Yaren", - "lat": -0.53333333, - "lng": 166.91666666, - "cca3": "NRU" - }, - { - "name": "Cuba", - "area": 109884, - "cioc": "CUB", - "cca2": "CU", - "capital": "Havana", - "lat": 21.5, - "lng": -80, - "cca3": "CUB" - } -] - -all_lookups = {} -lookups = ['cioc', 'cca2', 'cca3', 'name'] -for lookup in lookups: - all_lookups[lookup] = {} - for country in countries: - all_lookups[lookup][country[lookup].lower()] = country - -def get(field, symbol): - """ - Get country data based on a standard code and a symbol - - >>> get('cioc', 'CUB')['name'] - "Cuba" - >>> get('cca2', 'CA')['name'] - "Canada" - """ - return all_lookups[field].get(symbol.lower()) diff --git a/panoramix/forms.py b/panoramix/forms.py deleted file mode 100644 index 233ed65a5037..000000000000 --- a/panoramix/forms.py +++ /dev/null @@ -1,588 +0,0 @@ -from wtforms import ( - Form, SelectMultipleField, SelectField, TextField, TextAreaField, - BooleanField, IntegerField, HiddenField) -from wtforms import validators, widgets -from copy import copy -from panoramix import app -from collections import OrderedDict -config = app.config - - -class BetterBooleanField(BooleanField): - - """ - Fixes behavior of html forms omitting non checked - (which doesn't distinguish False from NULL/missing ) - If value is unchecked, this hidden fills in False value - """ - - def __call__(self, **kwargs): - html = super(BetterBooleanField, self).__call__(**kwargs) - html += u''.format(self.name) - return widgets.HTMLString(html) - - -class SelectMultipleSortableField(SelectMultipleField): - - """Works along with select2sortable to preserves the sort order""" - - def iter_choices(self): - d = OrderedDict() - for value, label in self.choices: - selected = self.data is not None and self.coerce(value) in self.data - d[value] = (value, label, selected) - if self.data: - for value in self.data: - if value: - yield d.pop(value) - while d: - yield d.pop(d.keys()[0]) - - -class FreeFormSelect(widgets.Select): - - """A WTF widget that allows for free form entry""" - - def __call__(self, field, **kwargs): - kwargs.setdefault('id', field.id) - if self.multiple: - kwargs['multiple'] = True - html = ['') - return widgets.HTMLString(''.join(html)) - - -class FreeFormSelectField(SelectField): - - """ A WTF SelectField that allows for free form input """ - - widget = FreeFormSelect() - def pre_validate(self, form): - return - - -class OmgWtForm(Form): - - """Panoramixification of the WTForm Form object""" - - fieldsets = {} - css_classes = dict() - - def get_field(self, fieldname): - return getattr(self, fieldname) - - def field_css_classes(self, fieldname): - if fieldname in self.css_classes: - return " ".join(self.css_classes[fieldname]) - return "" - - -class FormFactory(object): - """Used to create the forms in the explore view dynamically""" - series_limits = [0, 5, 10, 25, 50, 100, 500] - fieltype_class = { - SelectField: 'select2', - SelectMultipleField: 'select2', - FreeFormSelectField: 'select2_freeform', - SelectMultipleSortableField: 'select2Sortable', - } - - def __init__(self, viz): - self.viz = viz - from panoramix.viz import viz_types - viz = self.viz - datasource = viz.datasource - default_metric = datasource.metrics_combo[0][0] - default_groupby = datasource.groupby_column_names[0] - group_by_choices = [(s, s) for s in datasource.groupby_column_names] - # Pool of all the fields that can be used in Panoramix - self.field_dict = { - 'viz_type': SelectField( - 'Viz', - default='table', - choices=[(k, v.verbose_name) for k, v in viz_types.items()], - description="The type of visualization to display"), - 'metrics': SelectMultipleSortableField( - 'Metrics', choices=datasource.metrics_combo, - default=[default_metric], - description="One or many metrics to display"), - 'metric': SelectField( - 'Metric', choices=datasource.metrics_combo, - default=default_metric, - description="Chose the metric"), - 'stacked_style': SelectField( - 'Chart Style', choices=self.choicify( - ['stack', 'stream', 'expand']), - default='stack', - description=""), - 'linear_color_scheme': SelectField( - 'Color Scheme', choices=self.choicify([ - 'fire', 'blue_white_yellow', 'white_black', - 'black_white']), - default='fire', - description=""), - 'normalize_across': SelectField( - 'Normalize Across', choices=self.choicify([ - 'heatmap', 'x', 'y']), - default='heatmap', - description=( - "Color will be rendered based on a ratio " - "of the cell against the sum of across this " - "criteria")), - 'canvas_image_rendering': SelectField( - 'Rendering', choices=( - ('pixelated', 'pixelated (Sharp)'), - ('auto', 'auto (Smooth)'), - ), - default='pixelated', - description=( - "image-rendering CSS attribute of the canvas object that " - "defines how the browser scales up the image")), - 'xscale_interval': SelectField( - 'XScale Interval', choices=self.choicify(range(1, 50)), - default='1', - description=( - "Number of step to take between ticks when " - "printing the x scale")), - 'yscale_interval': SelectField( - 'YScale Interval', choices=self.choicify(range(1, 50)), - default='1', - description=( - "Number of step to take between ticks when " - "printing the y scale")), - 'bar_stacked': BetterBooleanField( - 'Stacked Bars', - default=False, - description=""), - 'secondary_metric': SelectField( - 'Color Metric', choices=datasource.metrics_combo, - default=default_metric, - description="A metric to use for color"), - 'country_fieldtype': SelectField( - 'Country Field Type', - default='cca2', - choices=( - ('name', 'Full name'), - ('cioc', 'code International Olympic Committee (cioc)'), - ('cca2', 'code ISO 3166-1 alpha-2 (cca2)'), - ('cca3', 'code ISO 3166-1 alpha-3 (cca3)'), - ), - description=( - "The country code standard that Panoramix should expect " - "to find in the [country] column")), - 'groupby': SelectMultipleSortableField( - 'Group by', - choices=self.choicify(datasource.groupby_column_names), - description="One or many fields to group by"), - 'columns': SelectMultipleSortableField( - 'Columns', - choices=self.choicify(datasource.groupby_column_names), - description="One or many fields to pivot as columns"), - 'all_columns': SelectMultipleSortableField( - 'Columns', - choices=self.choicify(datasource.column_names), - description="Columns to display"), - 'all_columns_x': SelectField( - 'X', - choices=self.choicify(datasource.column_names), - description="Columns to display"), - 'all_columns_y': SelectField( - 'Y', - choices=self.choicify(datasource.column_names), - description="Columns to display"), - 'granularity': FreeFormSelectField( - 'Time Granularity', default="one day", - choices=self.choicify([ - 'all', - '5 seconds', - '30 seconds', - '1 minute', - '5 minutes', - '1 hour', - '6 hour', - '1 day', - '7 days', - ]), - description=( - "The time granularity for the visualization. Note that you " - "can type and use simple natural language as in '10 seconds', " - "'1 day' or '56 weeks'")), - 'link_length': FreeFormSelectField( - 'Link Length', default="200", - choices=self.choicify([ - '10', - '25', - '50', - '75', - '100', - '150', - '200', - '250', - ]), - description="Link length in the force layout"), - 'charge': FreeFormSelectField( - 'Charge', default="-500", - choices=self.choicify([ - '-50', - '-75', - '-100', - '-150', - '-200', - '-250', - '-500', - '-1000', - '-2500', - '-5000', - ]), - description="Charge in the force layout"), - 'granularity_sqla': SelectField( - 'Time Column', - default=datasource.main_dttm_col or datasource.any_dttm_col, - choices=self.choicify(datasource.dttm_cols), - description=( - "The time column for the visualization. Note that you " - "can define arbitrary expression that return a DATETIME " - "column in the table editor. Also note that the " - "filter bellow is applied against this column or " - "expression")), - 'resample_rule': FreeFormSelectField( - 'Resample Rule', default='', - choices=self.choicify(('1T', '1H', '1D', '7D', '1M', '1AS')), - description=("Pandas resample rule")), - 'resample_how': FreeFormSelectField( - 'Resample How', default='', - choices=self.choicify(('', 'mean', 'sum', 'median')), - description=("Pandas resample how")), - 'resample_fillmethod': FreeFormSelectField( - 'Resample Fill Method', default='', - choices=self.choicify(('', 'ffill', 'bfill')), - description=("Pandas resample fill method")), - 'since': FreeFormSelectField( - 'Since', default="7 days ago", - choices=self.choicify([ - '1 hour ago', - '12 hours ago', - '1 day ago', - '7 days ago', - '28 days ago', - '90 days ago', - '1 year ago' - ]), - description=( - "Timestamp from filter. This supports free form typing and " - "natural language as in '1 day ago', '28 days' or '3 years'")), - 'until': FreeFormSelectField('Until', default="now", - choices=self.choicify([ - 'now', - '1 day ago', - '7 days ago', - '28 days ago', - '90 days ago', - '1 year ago']) - ), - 'max_bubble_size': FreeFormSelectField( - 'Max Bubble Size', default="25", - choices=self.choicify([ - '5', - '10', - '15', - '25', - '50', - '75', - '100', - ]) - ), - 'row_limit': - FreeFormSelectField( - 'Row limit', - default=config.get("ROW_LIMIT"), - choices=self.choicify( - [10, 50, 100, 250, 500, 1000, 5000, 10000, 50000])), - 'limit': - FreeFormSelectField( - 'Series limit', - choices=self.choicify(self.series_limits), - default=50, - description=( - "Limits the number of time series that get displayed")), - 'rolling_type': SelectField( - 'Rolling', - default='None', - choices=[(s, s) for s in ['None', 'mean', 'sum', 'std', 'cumsum']], - description=( - "Defines a rolling window function to apply, works along " - "with the [Periods] text box")), - 'rolling_periods': IntegerField( - 'Periods', - validators=[validators.optional()], - description=( - "Defines the size of the rolling window function, " - "relative to the time granularity selected")), - 'series': SelectField( - 'Series', choices=group_by_choices, - default=default_groupby, - description=( - "Defines the grouping of entities. " - "Each serie is shown as a specific color on the chart and " - "has a legend toggle")), - 'entity': SelectField('Entity', choices=group_by_choices, - default=default_groupby, - description="This define the element to be plotted on the chart"), - 'x': SelectField( - 'X Axis', choices=datasource.metrics_combo, - default=default_metric, - description="Metric assigned to the [X] axis"), - 'y': SelectField('Y Axis', choices=datasource.metrics_combo, - default=default_metric, - description="Metric assigned to the [Y] axis"), - 'size': SelectField( - 'Bubble Size', - default=default_metric, - choices=datasource.metrics_combo), - 'url': TextField( - 'URL', default='www.airbnb.com',), - 'where': TextField( - 'Custom WHERE clause', default='', - description=( - "The text in this box gets included in your query's WHERE " - "clause, as an AND to other criteria. You can include " - "complex expression, parenthesis and anything else " - "supported by the backend it is directed towards.")), - 'having': TextField('Custom HAVING clause', default='', - description=( - "The text in this box gets included in your query's HAVING" - " clause, as an AND to other criteria. You can include " - "complex expression, parenthesis and anything else " - "supported by the backend it is directed towards.")), - 'compare_lag': TextField('Comparison Period Lag', - description=( - "Based on granularity, number of time periods to " - "compare against")), - 'compare_suffix': TextField('Comparison suffix', - description="Suffix to apply after the percentage display"), - 'x_axis_format': FreeFormSelectField('X axis format', - default='smart_date', - choices=[ - ('smart_date', 'Adaptative formating'), - ("%m/%d/%Y", '"%m/%d/%Y" | 01/14/2019'), - ("%Y-%m-%d", '"%Y-%m-%d" | 2019-01-14'), - ("%Y-%m-%d %H:%M:%S", - '"%Y-%m-%d %H:%M:%S" | 2019-01-14 01:32:10'), - ("%H:%M:%S", '"%H:%M:%S" | 01:32:10'), - ], - description="D3 format syntax for y axis " - "https://github.com/mbostock/\n" - "d3/wiki/Formatting"), - 'y_axis_format': FreeFormSelectField('Y axis format', - default='.3s', - choices=[ - ('.3s', '".3s" | 12.3k'), - ('.3%', '".3%" | 1234543.210%'), - ('.4r', '".4r" | 12350'), - ('.3f', '".3f" | 12345.432'), - ('+,', '"+," | +12,345.4321'), - ('$,.2f', '"$,.2f" | $12,345.43'), - ], - description="D3 format syntax for y axis " - "https://github.com/mbostock/\n" - "d3/wiki/Formatting"), - 'markup_type': SelectField( - "Markup Type", - choices=self.choicify(['markdown', 'html']), - default="markdown", - description="Pick your favorite markup language"), - 'rotation': SelectField( - "Rotation", - choices=[(s, s) for s in ['random', 'flat', 'square']], - default="random", - description="Rotation to apply to words in the cloud"), - 'line_interpolation': SelectField( - "Line Style", - choices=self.choicify([ - 'linear', 'basis', 'cardinal', 'monotone', - 'step-before', 'step-after']), - default='linear', - description="Line interpolation as defined by d3.js"), - 'code': TextAreaField( - "Code", description="Put your code here", default=''), - 'pandas_aggfunc': SelectField( - "Aggregation function", - choices=self.choicify([ - 'sum', 'mean', 'min', 'max', 'median', 'stdev', 'var']), - default='sum', - description=( - "Aggregate function to apply when pivoting and " - "computing the total rows and columns")), - 'size_from': TextField( - "Font Size From", - default="20", - description="Font size for the smallest value in the list"), - 'size_to': TextField( - "Font Size To", - default="150", - description="Font size for the biggest value in the list"), - 'show_brush': BetterBooleanField( - "Range Filter", default=False, - description=( - "Whether to display the time range interactive selector")), - 'show_datatable': BetterBooleanField( - "Data Table", default=False, - description="Whether to display the interactive data table"), - 'include_search': BetterBooleanField( - "Search Box", default=False, - description=( - "Whether to include a client side search box")), - 'show_bubbles': BetterBooleanField( - "Show Bubbles", default=False, - description=( - "Whether to display bubbles on top of countries")), - 'show_legend': BetterBooleanField( - "Legend", default=True, - description="Whether to display the legend (toggles)"), - 'x_axis_showminmax': BetterBooleanField( - "X bounds", default=True, - description=( - "Whether to display the min and max values of the X axis")), - 'rich_tooltip': BetterBooleanField( - "Rich Tooltip", default=True, - description=( - "The rich tooltip shows a list of all series for that" - " point in time")), - 'y_axis_zero': BetterBooleanField( - "Y Axis Zero", default=False, - description=( - "Force the Y axis to start at 0 instead of the minimum " - "value")), - 'y_log_scale': BetterBooleanField( - "Y Log", default=False, - description="Use a log scale for the Y axis"), - 'x_log_scale': BetterBooleanField( - "X Log", default=False, - description="Use a log scale for the X axis"), - 'donut': BetterBooleanField( - "Donut", default=False, - description="Do you want a donut or a pie?"), - 'contribution': BetterBooleanField( - "Contribution", default=False, - description="Compute the contribution to the total"), - 'num_period_compare': IntegerField( - "Period Ratio", default=None, - validators=[validators.optional()], - description=( - "[integer] Number of period to compare against, " - "this is relative to the granularity selected")), - 'time_compare': TextField( - "Time Shift", - default="", - description=( - "Overlay a timeseries from a " - "relative time period. Expects relative time delta " - "in natural language (example: 24 hours, 7 days, " - "56 weeks, 365 days")), - } - - @staticmethod - def choicify(l): - return [("{}".format(obj), "{}".format(obj)) for obj in l] - - def get_form(self): - """Returns a form object based on the viz/datasource/context""" - viz = self.viz - field_css_classes = {} - for name, obj in self.field_dict.items(): - field_css_classes[name] = ['form-control'] - s = self.fieltype_class.get(obj.field_class) - if s: - field_css_classes[name] += [s] - - for field in ('show_brush', 'show_legend', 'rich_tooltip'): - field_css_classes[field] += ['input-sm'] - - class QueryForm(OmgWtForm): - fieldsets = copy(viz.fieldsets) - css_classes = field_css_classes - standalone = HiddenField() - async = HiddenField() - extra_filters = HiddenField() - json = HiddenField() - slice_id = HiddenField() - slice_name = HiddenField() - previous_viz_type = HiddenField(default=viz.viz_type) - collapsed_fieldsets = HiddenField() - viz_type = self.field_dict.get('viz_type') - - filter_cols = viz.datasource.filterable_column_names or [''] - for i in range(10): - setattr(QueryForm, 'flt_col_' + str(i), SelectField( - 'Filter 1', - default=filter_cols[0], - choices=self.choicify(filter_cols))) - setattr(QueryForm, 'flt_op_' + str(i), SelectField( - 'Filter 1', - default='in', - choices=self.choicify(['in', 'not in']))) - setattr( - QueryForm, 'flt_eq_' + str(i), - TextField("Super", default='')) - - for field in viz.flat_form_fields(): - setattr(QueryForm, field, self.field_dict[field]) - - def add_to_form(attrs): - for attr in attrs: - setattr(QueryForm, attr, self.field_dict[attr]) - - # datasource type specific form elements - if viz.datasource.__class__.__name__ == 'SqlaTable': - QueryForm.fieldsets += ({ - 'label': 'SQL', - 'fields': ['where', 'having'], - 'description': ( - "This section exposes ways to include snippets of " - "SQL in your query"), - },) - add_to_form(('where', 'having')) - grains = viz.datasource.database.grains() - - if not viz.datasource.any_dttm_col: - return QueryForm - if grains: - time_fields = ('granularity_sqla', 'time_grain_sqla') - self.field_dict['time_grain_sqla'] = SelectField( - 'Time Grain', - choices=self.choicify((grain.name for grain in grains)), - default="Time Column", - description=( - "The time granularity for the visualization. This " - "applies a date transformation to alter " - "your time column and defines a new time granularity." - "The options here are defined on a per database " - "engine basis in the Panoramix source code")) - add_to_form(time_fields) - field_css_classes['time_grain_sqla'] = ['form-control', 'select2'] - field_css_classes['granularity_sqla'] = ['form-control', 'select2'] - else: - time_fields = 'granularity_sqla' - add_to_form((time_fields, )) - else: - time_fields = 'granularity' - add_to_form(('granularity',)) - field_css_classes['granularity'] = ['form-control', 'select2'] - add_to_form(('since', 'until')) - - QueryForm.fieldsets = ({ - 'label': 'Time', - 'fields': ( - time_fields, - ('since', 'until'), - ), - 'description': "Time related form attributes", - },) + tuple(QueryForm.fieldsets) - return QueryForm diff --git a/panoramix/migrations/README b/panoramix/migrations/README deleted file mode 100755 index 98e4f9c44eff..000000000000 --- a/panoramix/migrations/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/panoramix/migrations/__init__.py b/panoramix/migrations/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/panoramix/migrations/alembic.ini b/panoramix/migrations/alembic.ini deleted file mode 100644 index f8ed4801f78b..000000000000 --- a/panoramix/migrations/alembic.ini +++ /dev/null @@ -1,45 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/panoramix/migrations/env.py b/panoramix/migrations/env.py deleted file mode 100755 index e3713a3e49c3..000000000000 --- a/panoramix/migrations/env.py +++ /dev/null @@ -1,88 +0,0 @@ -from __future__ import with_statement -from alembic import context -from sqlalchemy import engine_from_config, pool -from logging.config import fileConfig -import logging -from flask.ext.appbuilder import Base - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -from flask import current_app -config.set_main_option('sqlalchemy.url', - current_app.config.get('SQLALCHEMY_DATABASE_URI')) -target_metadata = Base.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure(url=url) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - - # this callback is used to prevent an auto-migration from being generated - # when there are no changes to the schema - # reference: http://alembic.readthedocs.org/en/latest/cookbook.html - def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): - script = directives[0] - if script.upgrade_ops.is_empty(): - directives[:] = [] - logger.info('No changes in schema detected.') - - engine = engine_from_config(config.get_section(config.config_ini_section), - prefix='sqlalchemy.', - poolclass=pool.NullPool) - - connection = engine.connect() - context.configure(connection=connection, - target_metadata=target_metadata, - #compare_type=True, - process_revision_directives=process_revision_directives, - **current_app.extensions['migrate'].configure_args) - - try: - with context.begin_transaction(): - context.run_migrations() - finally: - connection.close() - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/panoramix/migrations/script.py.mako b/panoramix/migrations/script.py.mako deleted file mode 100755 index 95702017ea34..000000000000 --- a/panoramix/migrations/script.py.mako +++ /dev/null @@ -1,22 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision} -Create Date: ${create_date} - -""" - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/panoramix/migrations/versions/12d55656cbca_is_featured.py b/panoramix/migrations/versions/12d55656cbca_is_featured.py deleted file mode 100644 index 315822374306..000000000000 --- a/panoramix/migrations/versions/12d55656cbca_is_featured.py +++ /dev/null @@ -1,23 +0,0 @@ -"""is_featured - -Revision ID: 12d55656cbca -Revises: 55179c7f25c7 -Create Date: 2015-12-14 13:37:17.374852 - -""" - -# revision identifiers, used by Alembic. -revision = '12d55656cbca' -down_revision = '55179c7f25c7' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.add_column('tables', sa.Column('is_featured', sa.Boolean(), nullable=True)) - - -def downgrade(): - op.drop_column('tables', 'is_featured') - diff --git a/panoramix/migrations/versions/18e88e1cc004_making_audit_nullable.py b/panoramix/migrations/versions/18e88e1cc004_making_audit_nullable.py deleted file mode 100644 index 0143aad58722..000000000000 --- a/panoramix/migrations/versions/18e88e1cc004_making_audit_nullable.py +++ /dev/null @@ -1,99 +0,0 @@ -"""making audit nullable - -Revision ID: 18e88e1cc004 -Revises: 430039611635 -Create Date: 2016-03-13 21:30:24.833107 - -""" - -# revision identifiers, used by Alembic. -revision = '18e88e1cc004' -down_revision = '430039611635' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - try: - op.alter_column( - 'clusters', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column( - 'clusters', 'created_on', - existing_type=sa.DATETIME(), nullable=True) - op.drop_constraint(None, 'columns', type_='foreignkey') - op.drop_constraint(None, 'columns', type_='foreignkey') - op.drop_column('columns', 'created_on') - op.drop_column('columns', 'created_by_fk') - op.drop_column('columns', 'changed_on') - op.drop_column('columns', 'changed_by_fk') - op.alter_column('css_templates', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('css_templates', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('dashboards', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('dashboards', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.create_unique_constraint(None, 'dashboards', ['slug']) - op.alter_column('datasources', 'changed_by_fk', - existing_type=sa.INTEGER(), - nullable=True) - op.alter_column('datasources', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('datasources', 'created_by_fk', - existing_type=sa.INTEGER(), - nullable=True) - op.alter_column('datasources', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('dbs', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('dbs', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('slices', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('slices', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('sql_metrics', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('sql_metrics', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('table_columns', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('table_columns', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('tables', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('tables', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('url', 'changed_on', - existing_type=sa.DATETIME(), - nullable=True) - op.alter_column('url', 'created_on', - existing_type=sa.DATETIME(), - nullable=True) - ### end Alembic commands ### - except: - pass - - -def downgrade(): - pass diff --git a/panoramix/migrations/versions/1a48a5411020_adding_slug_to_dash.py b/panoramix/migrations/versions/1a48a5411020_adding_slug_to_dash.py deleted file mode 100644 index c6b88642b254..000000000000 --- a/panoramix/migrations/versions/1a48a5411020_adding_slug_to_dash.py +++ /dev/null @@ -1,26 +0,0 @@ -"""adding slug to dash - -Revision ID: 1a48a5411020 -Revises: 289ce07647b -Create Date: 2015-12-04 09:42:16.973264 - -""" - -# revision identifiers, used by Alembic. -revision = '1a48a5411020' -down_revision = '289ce07647b' - -from alembic import op -import sqlalchemy as sa - -def upgrade(): - op.add_column('dashboards', sa.Column('slug', sa.String(length=255), nullable=True)) - try: - op.create_unique_constraint('idx_unique_slug', 'dashboards', ['slug']) - except: - pass - - -def downgrade(): - op.drop_constraint(None, 'dashboards', type_='unique') - op.drop_column('dashboards', 'slug') diff --git a/panoramix/migrations/versions/1e2841a4128_.py b/panoramix/migrations/versions/1e2841a4128_.py deleted file mode 100644 index 330b3b217c01..000000000000 --- a/panoramix/migrations/versions/1e2841a4128_.py +++ /dev/null @@ -1,21 +0,0 @@ -"""empty message - -Revision ID: 1e2841a4128 -Revises: 5a7bad26f2a7 -Create Date: 2015-10-05 22:11:00.537054 - -""" - -# revision identifiers, used by Alembic. -revision = '1e2841a4128' -down_revision = '5a7bad26f2a7' - -from alembic import op -import sqlalchemy as sa - -def upgrade(): - op.add_column('table_columns', sa.Column('expression', sa.Text(), nullable=True)) - - -def downgrade(): - op.drop_column('table_columns', 'expression') diff --git a/panoramix/migrations/versions/2591d77e9831_user_id.py b/panoramix/migrations/versions/2591d77e9831_user_id.py deleted file mode 100644 index 4fac61ce9eb3..000000000000 --- a/panoramix/migrations/versions/2591d77e9831_user_id.py +++ /dev/null @@ -1,26 +0,0 @@ -"""user_id - -Revision ID: 2591d77e9831 -Revises: 12d55656cbca -Create Date: 2015-12-15 17:02:45.128709 - -""" - -# revision identifiers, used by Alembic. -revision = '2591d77e9831' -down_revision = '12d55656cbca' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - with op.batch_alter_table('tables') as batch_op: - batch_op.add_column(sa.Column('user_id', sa.Integer())) - batch_op.create_foreign_key('user_id', 'ab_user', ['user_id'], ['id']) - - -def downgrade(): - with op.batch_alter_table('tables') as batch_op: - batch_op.drop_constraint('user_id', type_='foreignkey') - batch_op.drop_column('user_id') diff --git a/panoramix/migrations/versions/289ce07647b_add_encrypted_password_field.py b/panoramix/migrations/versions/289ce07647b_add_encrypted_password_field.py deleted file mode 100644 index 6d64887b2ff3..000000000000 --- a/panoramix/migrations/versions/289ce07647b_add_encrypted_password_field.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Add encrypted password field - -Revision ID: 289ce07647b -Revises: 2929af7925ed -Create Date: 2015-11-21 11:18:00.650587 - -""" - -# revision identifiers, used by Alembic. -revision = '289ce07647b' -down_revision = '2929af7925ed' - -from alembic import op -import sqlalchemy as sa -from sqlalchemy_utils.types.encrypted import EncryptedType - - -def upgrade(): - op.add_column( - 'dbs', - sa.Column( - 'password', - EncryptedType(sa.String(1024)), - nullable=True)) - - -def downgrade(): - op.drop_column('dbs', 'password') diff --git a/panoramix/migrations/versions/2929af7925ed_tz_offsets_in_data_sources.py b/panoramix/migrations/versions/2929af7925ed_tz_offsets_in_data_sources.py deleted file mode 100644 index 85b54bc5cc31..000000000000 --- a/panoramix/migrations/versions/2929af7925ed_tz_offsets_in_data_sources.py +++ /dev/null @@ -1,23 +0,0 @@ -"""TZ offsets in data sources - -Revision ID: 2929af7925ed -Revises: 1e2841a4128 -Create Date: 2015-10-19 20:54:00.565633 - -""" - -# revision identifiers, used by Alembic. -revision = '2929af7925ed' -down_revision = '1e2841a4128' - -from alembic import op -import sqlalchemy as sa - -def upgrade(): - op.add_column('datasources', sa.Column('offset', sa.Integer(), nullable=True)) - op.add_column('tables', sa.Column('offset', sa.Integer(), nullable=True)) - - -def downgrade(): - op.drop_column('tables', 'offset') - op.drop_column('datasources', 'offset') diff --git a/panoramix/migrations/versions/315b3f4da9b0_adding_log_model.py b/panoramix/migrations/versions/315b3f4da9b0_adding_log_model.py deleted file mode 100644 index d9fdfaccea1b..000000000000 --- a/panoramix/migrations/versions/315b3f4da9b0_adding_log_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""adding log model - -Revision ID: 315b3f4da9b0 -Revises: 1a48a5411020 -Create Date: 2015-12-04 11:16:58.226984 - -""" - -# revision identifiers, used by Alembic. -revision = '315b3f4da9b0' -down_revision = '1a48a5411020' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.create_table('logs', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('action', sa.String(length=512), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('json', sa.Text(), nullable=True), - sa.Column('dttm', sa.DateTime(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - - -def downgrade(): - op.drop_table('logs') diff --git a/panoramix/migrations/versions/430039611635_log_more.py b/panoramix/migrations/versions/430039611635_log_more.py deleted file mode 100644 index aec2b32ed95c..000000000000 --- a/panoramix/migrations/versions/430039611635_log_more.py +++ /dev/null @@ -1,23 +0,0 @@ -"""log more - -Revision ID: 430039611635 -Revises: d827694c7555 -Create Date: 2016-02-10 08:47:28.950891 - -""" - -# revision identifiers, used by Alembic. -revision = '430039611635' -down_revision = 'd827694c7555' - -from alembic import op -import sqlalchemy as sa - -def upgrade(): - op.add_column('logs', sa.Column('dashboard_id', sa.Integer(), nullable=True)) - op.add_column('logs', sa.Column('slice_id', sa.Integer(), nullable=True)) - - -def downgrade(): - op.drop_column('logs', 'slice_id') - op.drop_column('logs', 'dashboard_id') diff --git a/panoramix/migrations/versions/43df8de3a5f4_dash_json.py b/panoramix/migrations/versions/43df8de3a5f4_dash_json.py deleted file mode 100644 index c56ddc8f5fb2..000000000000 --- a/panoramix/migrations/versions/43df8de3a5f4_dash_json.py +++ /dev/null @@ -1,22 +0,0 @@ -"""empty message - -Revision ID: 43df8de3a5f4 -Revises: 7dbf98566af7 -Create Date: 2016-01-18 23:43:16.073483 - -""" - -# revision identifiers, used by Alembic. -revision = '43df8de3a5f4' -down_revision = '7dbf98566af7' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.add_column('dashboards', sa.Column('json_metadata', sa.Text(), nullable=True)) - - -def downgrade(): - op.drop_column('dashboards', 'json_metadata') diff --git a/panoramix/migrations/versions/4e6a06bad7a8_init.py b/panoramix/migrations/versions/4e6a06bad7a8_init.py deleted file mode 100644 index 3b8a9bff452d..000000000000 --- a/panoramix/migrations/versions/4e6a06bad7a8_init.py +++ /dev/null @@ -1,215 +0,0 @@ -"""Init - -Revision ID: 4e6a06bad7a8 -Revises: None -Create Date: 2015-09-21 17:30:38.442998 - -""" - -# revision identifiers, used by Alembic. -revision = '4e6a06bad7a8' -down_revision = None - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.create_table('clusters', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('cluster_name', sa.String(length=250), nullable=True), - sa.Column('coordinator_host', sa.String(length=256), nullable=True), - sa.Column('coordinator_port', sa.Integer(), nullable=True), - sa.Column('coordinator_endpoint', sa.String(length=256), nullable=True), - sa.Column('broker_host', sa.String(length=256), nullable=True), - sa.Column('broker_port', sa.Integer(), nullable=True), - sa.Column('broker_endpoint', sa.String(length=256), nullable=True), - sa.Column('metadata_last_refreshed', sa.DateTime(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('cluster_name') - ) - op.create_table('dashboards', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('dashboard_title', sa.String(length=500), nullable=True), - sa.Column('position_json', sa.Text(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('dbs', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('database_name', sa.String(length=250), nullable=True), - sa.Column('sqlalchemy_uri', sa.String(length=1024), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('database_name') - ) - op.create_table('datasources', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('datasource_name', sa.String(length=250), nullable=True), - sa.Column('is_featured', sa.Boolean(), nullable=True), - sa.Column('is_hidden', sa.Boolean(), nullable=True), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('default_endpoint', sa.Text(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('cluster_name', sa.String(length=250), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=False), - sa.Column('created_by_fk', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['cluster_name'], ['clusters.cluster_name'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('datasource_name') - ) - op.create_table('tables', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('table_name', sa.String(length=250), nullable=True), - sa.Column('main_dttm_col', sa.String(length=250), nullable=True), - sa.Column('default_endpoint', sa.Text(), nullable=True), - sa.Column('database_id', sa.Integer(), nullable=False), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['database_id'], ['dbs.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('table_name') - ) - op.create_table('columns', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('datasource_name', sa.String(length=250), nullable=True), - sa.Column('column_name', sa.String(length=256), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=True), - sa.Column('type', sa.String(length=32), nullable=True), - sa.Column('groupby', sa.Boolean(), nullable=True), - sa.Column('count_distinct', sa.Boolean(), nullable=True), - sa.Column('sum', sa.Boolean(), nullable=True), - sa.Column('max', sa.Boolean(), nullable=True), - sa.Column('min', sa.Boolean(), nullable=True), - sa.Column('filterable', sa.Boolean(), nullable=True), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['datasource_name'], ['datasources.datasource_name'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('metrics', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('metric_name', sa.String(length=512), nullable=True), - sa.Column('verbose_name', sa.String(length=1024), nullable=True), - sa.Column('metric_type', sa.String(length=32), nullable=True), - sa.Column('datasource_name', sa.String(length=250), nullable=True), - sa.Column('json', sa.Text(), nullable=True), - sa.Column('description', sa.Text(), nullable=True), - sa.ForeignKeyConstraint(['datasource_name'], ['datasources.datasource_name'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('slices', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('slice_name', sa.String(length=250), nullable=True), - sa.Column('druid_datasource_id', sa.Integer(), nullable=True), - sa.Column('table_id', sa.Integer(), nullable=True), - sa.Column('datasource_type', sa.String(length=200), nullable=True), - sa.Column('datasource_name', sa.String(length=2000), nullable=True), - sa.Column('viz_type', sa.String(length=250), nullable=True), - sa.Column('params', sa.Text(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['druid_datasource_id'], ['datasources.id'], ), - sa.ForeignKeyConstraint(['table_id'], ['tables.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('sql_metrics', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('metric_name', sa.String(length=512), nullable=True), - sa.Column('verbose_name', sa.String(length=1024), nullable=True), - sa.Column('metric_type', sa.String(length=32), nullable=True), - sa.Column('table_id', sa.Integer(), nullable=True), - sa.Column('expression', sa.Text(), nullable=True), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['table_id'], ['tables.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('table_columns', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('table_id', sa.Integer(), nullable=True), - sa.Column('column_name', sa.String(length=256), nullable=True), - sa.Column('is_dttm', sa.Boolean(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=True), - sa.Column('type', sa.String(length=32), nullable=True), - sa.Column('groupby', sa.Boolean(), nullable=True), - sa.Column('count_distinct', sa.Boolean(), nullable=True), - sa.Column('sum', sa.Boolean(), nullable=True), - sa.Column('max', sa.Boolean(), nullable=True), - sa.Column('min', sa.Boolean(), nullable=True), - sa.Column('filterable', sa.Boolean(), nullable=True), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['table_id'], ['tables.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('dashboard_slices', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('dashboard_id', sa.Integer(), nullable=True), - sa.Column('slice_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['dashboard_id'], ['dashboards.id'], ), - sa.ForeignKeyConstraint(['slice_id'], ['slices.id'], ), - sa.PrimaryKeyConstraint('id') - ) - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_table('dashboard_slices') - op.drop_table('table_columns') - op.drop_table('sql_metrics') - op.drop_table('slices') - op.drop_table('metrics') - op.drop_table('columns') - op.drop_table('tables') - op.drop_table('datasources') - op.drop_table('dbs') - op.drop_table('dashboards') - op.drop_table('clusters') - ### end Alembic commands ### diff --git a/panoramix/migrations/versions/55179c7f25c7_sqla_descr.py b/panoramix/migrations/versions/55179c7f25c7_sqla_descr.py deleted file mode 100644 index aade0b930aa0..000000000000 --- a/panoramix/migrations/versions/55179c7f25c7_sqla_descr.py +++ /dev/null @@ -1,22 +0,0 @@ -"""sqla_descr - -Revision ID: 55179c7f25c7 -Revises: 315b3f4da9b0 -Create Date: 2015-12-13 08:38:43.704145 - -""" - -# revision identifiers, used by Alembic. -revision = '55179c7f25c7' -down_revision = '315b3f4da9b0' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.add_column('tables', sa.Column('description', sa.Text(), nullable=True)) - - -def downgrade(): - op.drop_column('tables', 'description') diff --git a/panoramix/migrations/versions/5a7bad26f2a7_.py b/panoramix/migrations/versions/5a7bad26f2a7_.py deleted file mode 100644 index 66dc20aae35e..000000000000 --- a/panoramix/migrations/versions/5a7bad26f2a7_.py +++ /dev/null @@ -1,24 +0,0 @@ -"""empty message - -Revision ID: 5a7bad26f2a7 -Revises: 4e6a06bad7a8 -Create Date: 2015-10-05 10:32:15.850753 - -""" - -# revision identifiers, used by Alembic. -revision = '5a7bad26f2a7' -down_revision = '4e6a06bad7a8' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.add_column('dashboards', sa.Column('css', sa.Text(), nullable=True)) - op.add_column('dashboards', sa.Column('description', sa.Text(), nullable=True)) - - -def downgrade(): - op.drop_column('dashboards', 'description') - op.drop_column('dashboards', 'css') diff --git a/panoramix/migrations/versions/7dbf98566af7_slice_description.py b/panoramix/migrations/versions/7dbf98566af7_slice_description.py deleted file mode 100644 index 329af9ef2d78..000000000000 --- a/panoramix/migrations/versions/7dbf98566af7_slice_description.py +++ /dev/null @@ -1,20 +0,0 @@ -"""empty message - -Revision ID: 7dbf98566af7 -Revises: 8e80a26a31db -Create Date: 2016-01-17 22:00:23.640788 - -""" - -# revision identifiers, used by Alembic. -revision = '7dbf98566af7' -down_revision = '8e80a26a31db' - -from alembic import op -import sqlalchemy as sa - -def upgrade(): - op.add_column('slices', sa.Column('description', sa.Text(), nullable=True)) - -def downgrade(): - op.drop_column('slices', 'description') diff --git a/panoramix/migrations/versions/8e80a26a31db_.py b/panoramix/migrations/versions/8e80a26a31db_.py deleted file mode 100644 index 54edc58a80a3..000000000000 --- a/panoramix/migrations/versions/8e80a26a31db_.py +++ /dev/null @@ -1,32 +0,0 @@ -"""empty message - -Revision ID: 8e80a26a31db -Revises: 2591d77e9831 -Create Date: 2016-01-13 20:24:45.256437 - -""" - -# revision identifiers, used by Alembic. -revision = '8e80a26a31db' -down_revision = '2591d77e9831' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.create_table('url', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('url', sa.Text(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - - -def downgrade(): - op.drop_table('url') diff --git a/panoramix/migrations/versions/d827694c7555_css_templates.py b/panoramix/migrations/versions/d827694c7555_css_templates.py deleted file mode 100644 index 3b20e4405596..000000000000 --- a/panoramix/migrations/versions/d827694c7555_css_templates.py +++ /dev/null @@ -1,33 +0,0 @@ -"""css templates - -Revision ID: d827694c7555 -Revises: 43df8de3a5f4 -Create Date: 2016-02-03 17:41:10.944019 - -""" - -# revision identifiers, used by Alembic. -revision = 'd827694c7555' -down_revision = '43df8de3a5f4' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.create_table('css_templates', - sa.Column('created_on', sa.DateTime(), nullable=False), - sa.Column('changed_on', sa.DateTime(), nullable=False), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('template_name', sa.String(length=250), nullable=True), - sa.Column('css', sa.Text(), nullable=True), - sa.Column('changed_by_fk', sa.Integer(), nullable=True), - sa.Column('created_by_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ), - sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - - -def downgrade(): - op.drop_table('css_templates') diff --git a/panoramix/models.py b/panoramix/models.py deleted file mode 100644 index d64082e1bd91..000000000000 --- a/panoramix/models.py +++ /dev/null @@ -1,1198 +0,0 @@ -""" -A collection of ORM sqlalchemy models for Panoramix -""" - -from copy import deepcopy, copy -from collections import namedtuple -from datetime import timedelta, datetime -import functools -import json -import logging -from six import string_types -import sqlparse -import requests - -from dateutil.parser import parse -from flask import flash, request, g -from flask.ext.appbuilder import Model -from flask.ext.appbuilder.models.mixins import AuditMixin -import pandas as pd -from pydruid import client -from pydruid.utils.filters import Dimension, Filter - -import sqlalchemy as sqla -from sqlalchemy import ( - Column, Integer, String, ForeignKey, Text, Boolean, DateTime, - Table, create_engine, MetaData, desc, select, and_, func) -from sqlalchemy.engine import reflection -from sqlalchemy.orm import relationship -from sqlalchemy.sql import table, literal_column, text, column -from sqlalchemy.sql.elements import ColumnClause -from sqlalchemy_utils import EncryptedType - -from panoramix import app, db, get_session, utils -from panoramix.viz import viz_types -from sqlalchemy.ext.declarative import declared_attr - -config = app.config - -QueryResult = namedtuple('namedtuple', ['df', 'query', 'duration']) - - -class AuditMixinNullable(AuditMixin): - - """Altering the AuditMixin to use nullable fields - - Allows creating objects programmatically outside of CRUD - """ - - created_on = Column(DateTime, default=datetime.now, nullable=True) - changed_on = Column( - DateTime, default=datetime.now, - onupdate=datetime.now, nullable=True) - - @declared_attr - def created_by_fk(cls): - return Column(Integer, ForeignKey('ab_user.id'), - default=cls.get_user_id, nullable=True) - - @declared_attr - def changed_by_fk(cls): - return Column(Integer, ForeignKey('ab_user.id'), - default=cls.get_user_id, onupdate=cls.get_user_id, nullable=True) - - @property - def created_by_(self): - return '{}'.format(self.created_by or '') - - @property # noqa - def changed_by_(self): - return '{}'.format(self.changed_by or '') - - -class Url(Model, AuditMixinNullable): - - """Used for the short url feature""" - - __tablename__ = 'url' - id = Column(Integer, primary_key=True) - url = Column(Text) - - -class CssTemplate(Model, AuditMixinNullable): - - """CSS templates for dashboards""" - - __tablename__ = 'css_templates' - id = Column(Integer, primary_key=True) - template_name = Column(String(250)) - css = Column(Text, default='') - - -class Slice(Model, AuditMixinNullable): - - """A slice is essentially a report or a view on data""" - - __tablename__ = 'slices' - id = Column(Integer, primary_key=True) - slice_name = Column(String(250)) - druid_datasource_id = Column(Integer, ForeignKey('datasources.id')) - table_id = Column(Integer, ForeignKey('tables.id')) - datasource_type = Column(String(200)) - datasource_name = Column(String(2000)) - viz_type = Column(String(250)) - params = Column(Text) - description = Column(Text) - - table = relationship( - 'SqlaTable', foreign_keys=[table_id], backref='slices') - druid_datasource = relationship( - 'DruidDatasource', foreign_keys=[druid_datasource_id], backref='slices') - - def __repr__(self): - return self.slice_name - - @property - def datasource(self): - return self.table or self.druid_datasource - - @property - def datasource_link(self): - if self.table: - return self.table.link - elif self.druid_datasource: - return self.druid_datasource.link - - @property - @utils.memoized - def viz(self): - d = json.loads(self.params) - viz = viz_types[self.viz_type]( - self.datasource, - form_data=d) - return viz - - @property - def description_markeddown(self): - return utils.markdown(self.description) - - @property - def datasource_id(self): - return self.table_id or self.druid_datasource_id - - @property - def data(self): - d = self.viz.data - d['slice_id'] = self.id - return d - - @property - def json_data(self): - return json.dumps(self.data) - - @property - def slice_url(self): - """Defines the url to access the slice""" - try: - slice_params = json.loads(self.params) - except Exception as e: - logging.exception(e) - slice_params = {} - slice_params['slice_id'] = self.id - slice_params['slice_name'] = self.slice_name - from werkzeug.urls import Href - href = Href( - "/panoramix/explore/{self.datasource_type}/" - "{self.datasource_id}/".format(self=self)) - return href(slice_params) - - @property - def edit_url(self): - return "/slicemodelview/edit/{}".format(self.id) - - @property - def slice_link(self): - url = self.slice_url - return '{self.slice_name}'.format( - url=url, self=self) - - -dashboard_slices = Table('dashboard_slices', Model.metadata, - Column('id', Integer, primary_key=True), - Column('dashboard_id', Integer, ForeignKey('dashboards.id')), - Column('slice_id', Integer, ForeignKey('slices.id')), -) - - -class Dashboard(Model, AuditMixinNullable): - - """The dashboard object!""" - - __tablename__ = 'dashboards' - id = Column(Integer, primary_key=True) - dashboard_title = Column(String(500)) - position_json = Column(Text) - description = Column(Text) - css = Column(Text) - json_metadata = Column(Text) - slug = Column(String(255), unique=True) - slices = relationship( - 'Slice', secondary=dashboard_slices, backref='dashboards') - - def __repr__(self): - return self.dashboard_title - - @property - def url(self): - return "/panoramix/dashboard/{}/".format(self.slug or self.id) - - @property - def metadata_dejson(self): - if self.json_metadata: - return json.loads(self.json_metadata) - else: - return {} - - def dashboard_link(self): - return '{self.dashboard_title}'.format(self=self) - - @property - def json_data(self): - d = { - 'id': self.id, - 'metadata': self.metadata_dejson, - 'dashboard_title': self.dashboard_title, - 'slug': self.slug, - 'slices': [slc.data for slc in self.slices], - } - return json.dumps(d) - - -class Queryable(object): - """A common interface to objects that are queryable (tables and datasources)""" - @property - def column_names(self): - return sorted([c.column_name for c in self.columns]) - - @property - def main_dttm_col(self): - return "timestamp" - - @property - def groupby_column_names(self): - return sorted([c.column_name for c in self.columns if c.groupby]) - - @property - def filterable_column_names(self): - return sorted([c.column_name for c in self.columns if c.filterable]) - - @property - def dttm_cols(self): - return [] - - -class Database(Model, AuditMixinNullable): - - """An ORM object that stores Database related information""" - - __tablename__ = 'dbs' - id = Column(Integer, primary_key=True) - database_name = Column(String(250), unique=True) - sqlalchemy_uri = Column(String(1024)) - password = Column(EncryptedType(String(1024), config.get('SECRET_KEY'))) - - def __repr__(self): - return self.database_name - - def get_sqla_engine(self): - return create_engine(self.sqlalchemy_uri_decrypted) - - def safe_sqlalchemy_uri(self): - return self.sqlalchemy_uri - - def grains(self): - """Defines time granularity database-specific expressions. - - The idea here is to make it easy for users to change the time grain - form a datetime (maybe the source grain is arbitrary timestamps, daily - or 5 minutes increments) to another, "truncated" datetime. Since - each database has slightly different but similar datetime functions, - this allows a mapping between database engines and actual functions. - """ - Grain = namedtuple('Grain', 'name function') - DB_TIME_GRAINS = { - 'presto': ( - Grain('Time Column', '{col}'), - Grain('week', "date_trunc('week', CAST({col} AS DATE))"), - Grain('month', "date_trunc('month', CAST({col} AS DATE))"), - ), - 'mysql': ( - Grain('Time Column', '{col}'), - Grain('day', 'DATE({col})'), - Grain('week', 'DATE_SUB({col}, INTERVAL DAYOFWEEK({col}) - 1 DAY)'), - Grain('month', 'DATE_SUB({col}, INTERVAL DAYOFMONTH({col}) - 1 DAY)'), - ), - } - for db_type, grains in DB_TIME_GRAINS.items(): - if self.sqlalchemy_uri.startswith(db_type): - return grains - - def grains_dict(self): - return {grain.name: grain for grain in self.grains()} - - def get_table(self, table_name): - meta = MetaData() - return Table( - table_name, meta, - autoload=True, - autoload_with=self.get_sqla_engine()) - - def get_columns(self, table_name): - engine = self.get_sqla_engine() - insp = reflection.Inspector.from_engine(engine) - return insp.get_columns(table_name) - - @property - def sqlalchemy_uri_decrypted(self): - conn = sqla.engine.url.make_url(self.sqlalchemy_uri) - conn.password = self.password - return str(conn) - - @property - def sql_url(self): - return '/panoramix/sql/{}/'.format(self.id) - - @property - def sql_link(self): - return 'SQL'.format(self.sql_url) - - -class SqlaTable(Model, Queryable, AuditMixinNullable): - - """An ORM object for SqlAlchemy table references""" - - type = "table" - - __tablename__ = 'tables' - id = Column(Integer, primary_key=True) - table_name = Column(String(250), unique=True) - main_dttm_col = Column(String(250)) - description = Column(Text) - default_endpoint = Column(Text) - database_id = Column(Integer, ForeignKey('dbs.id'), nullable=False) - is_featured = Column(Boolean, default=False) - user_id = Column(Integer, ForeignKey('ab_user.id')) - owner = relationship('User', backref='tables', foreign_keys=[user_id]) - database = relationship( - 'Database', backref='tables', foreign_keys=[database_id]) - offset = Column(Integer, default=0) - - baselink = "tablemodelview" - - def __repr__(self): - return self.table_name - - @property - def description_markeddown(self): - return utils.markdown(self.description) - - @property - def url(self): - return '/tablemodelview/edit/{}'.format(self.id) - - @property - def link(self): - return '{self.table_name}'.format(**locals()) - - @property - def perm(self): - return ( - "[{self.database}].[{self.table_name}]" - "(id:{self.id})").format(self=self) - - @property - def full_name(self): - return "[{self.database}].[{self.table_name}]".format(self=self) - - @property - def dttm_cols(self): - l = [c.column_name for c in self.columns if c.is_dttm] - if self.main_dttm_col not in l: - l.append(self.main_dttm_col) - return l - - @property - def any_dttm_col(self): - cols = self.dttm_cols - if cols: - return cols[0] - - @property - def html(self): - t = ((c.column_name, c.type) for c in self.columns) - df = pd.DataFrame(t) - df.columns = ['field', 'type'] - return df.to_html( - index=False, - classes=( - "dataframe table table-striped table-bordered " - "table-condensed")) - - @property - def name(self): - return self.table_name - - @property - def table_link(self): - url = "/panoramix/explore/{self.type}/{self.id}/".format(self=self) - return '{self.table_name}'.format( - url=url, self=self) - - @property - def metrics_combo(self): - return sorted( - [ - (m.metric_name, m.verbose_name or m.metric_name) - for m in self.metrics], - key=lambda x: x[1]) - - @property - def sql_url(self): - return self.database.sql_url + "?table_name=" + str(self.table_name) - - @property - def sql_link(self): - return 'SQL'.format(self.sql_url) - - def query( - self, groupby, metrics, - granularity, - from_dttm, to_dttm, - filter=None, # noqa - is_timeseries=True, - timeseries_limit=15, row_limit=None, - inner_from_dttm=None, inner_to_dttm=None, - extras=None, - columns=None): - - # For backward compatibility - if granularity not in self.dttm_cols: - granularity = self.main_dttm_col - - cols = {col.column_name: col for col in self.columns} - qry_start_dttm = datetime.now() - - if not granularity and is_timeseries: - raise Exception( - "Datetime column not provided as part table configuration " - "and is required by this type of chart") - - metrics_exprs = [ - literal_column(m.expression).label(m.metric_name) - for m in self.metrics if m.metric_name in metrics] - - if metrics: - main_metric_expr = literal_column([ - m.expression for m in self.metrics - if m.metric_name == metrics[0]][0]) - else: - main_metric_expr = literal_column("COUNT(*)") - - select_exprs = [] - groupby_exprs = [] - - if groupby: - select_exprs = [] - inner_select_exprs = [] - inner_groupby_exprs = [] - for s in groupby: - col = cols[s] - expr = col.expression - if expr: - outer = literal_column(expr).label(s) - inner = literal_column(expr).label('__' + s) - else: - outer = column(s).label(s) - inner = column(s).label('__' + s) - - groupby_exprs.append(outer) - select_exprs.append(outer) - inner_groupby_exprs.append(inner) - inner_select_exprs.append(inner) - elif columns: - for s in columns: - select_exprs.append(s) - metrics_exprs = [] - - if granularity: - dttm_expr = cols[granularity].expression or granularity - timestamp = literal_column(dttm_expr).label('timestamp') - - # Transforming time grain into an expression based on configuration - time_grain_sqla = extras.get('time_grain_sqla') - if time_grain_sqla: - udf = self.database.grains_dict().get(time_grain_sqla, '{col}') - timestamp_grain = literal_column( - udf.function.format(col=dttm_expr)).label('timestamp') - else: - timestamp_grain = timestamp - - if is_timeseries: - select_exprs += [timestamp_grain] - groupby_exprs += [timestamp_grain] - - tf = '%Y-%m-%d %H:%M:%S.%f' - time_filter = [ - timestamp >= from_dttm.strftime(tf), - timestamp <= to_dttm.strftime(tf), - ] - inner_time_filter = copy(time_filter) - if inner_from_dttm: - inner_time_filter[0] = timestamp >= inner_from_dttm.strftime(tf) - if inner_to_dttm: - inner_time_filter[1] = timestamp <= inner_to_dttm.strftime(tf) - - select_exprs += metrics_exprs - qry = select(select_exprs) - from_clause = table(self.table_name) - if not columns: - qry = qry.group_by(*groupby_exprs) - - where_clause_and = [] - having_clause_and = [] - for col, op, eq in filter: - col_obj = cols[col] - if op in ('in', 'not in'): - values = eq.split(",") - if col_obj.expression: - cond = ColumnClause( - col_obj.expression, is_literal=True).in_(values) - else: - cond = column(col).in_(values) - if op == 'not in': - cond = ~cond - where_clause_and.append(cond) - if extras and 'where' in extras: - where_clause_and += [text(extras['where'])] - if extras and 'having' in extras: - having_clause_and += [text(extras['having'])] - if granularity: - qry = qry.where(and_(*(time_filter + where_clause_and))) - qry = qry.having(and_(*having_clause_and)) - if groupby: - qry = qry.order_by(desc(main_metric_expr)) - qry = qry.limit(row_limit) - - if timeseries_limit and groupby: - subq = select(inner_select_exprs) - subq = subq.select_from(table(self.table_name)) - subq = subq.where(and_(*(where_clause_and + inner_time_filter))) - subq = subq.group_by(*inner_groupby_exprs) - subq = subq.order_by(desc(main_metric_expr)) - subq = subq.limit(timeseries_limit) - on_clause = [] - for i, gb in enumerate(groupby): - on_clause.append( - groupby_exprs[i] == column("__" + gb)) - - from_clause = from_clause.join(subq.alias(), and_(*on_clause)) - - qry = qry.select_from(from_clause) - - engine = self.database.get_sqla_engine() - sql = "{}".format( - qry.compile(engine, compile_kwargs={"literal_binds": True})) - df = pd.read_sql_query( - sql=sql, - con=engine - ) - sql = sqlparse.format(sql, reindent=True) - return QueryResult( - df=df, duration=datetime.now() - qry_start_dttm, query=sql) - - def fetch_metadata(self): - """Fetches the metadata for the table and merges it in""" - table = self.database.get_table(self.table_name) - try: - table = self.database.get_table(self.table_name) - except Exception as e: - flash(str(e)) - flash( - "Table doesn't seem to exist in the specified database, " - "couldn't fetch column information", "danger") - return - - TC = TableColumn - M = SqlMetric - metrics = [] - any_date_col = None - for col in table.columns: - try: - datatype = str(col.type) - except Exception as e: - datatype = "UNKNOWN" - dbcol = ( - db.session - .query(TC) - .filter(TC.table == self) - .filter(TC.column_name == col.name) - .first() - ) - db.session.flush() - if not dbcol: - dbcol = TableColumn(column_name=col.name) - - if ( - str(datatype).startswith('VARCHAR') or - str(datatype).startswith('STRING')): - dbcol.groupby = True - dbcol.filterable = True - elif str(datatype).upper() in ('DOUBLE', 'FLOAT', 'INT', 'BIGINT'): - dbcol.sum = True - db.session.merge(self) - self.columns.append(dbcol) - - if not any_date_col and 'date' in datatype.lower(): - any_date_col = col.name - - quoted = "{}".format( - column(dbcol.column_name).compile(dialect=db.engine.dialect)) - if dbcol.sum: - metrics.append(M( - metric_name='sum__' + dbcol.column_name, - verbose_name='sum__' + dbcol.column_name, - metric_type='sum', - expression="SUM({})".format(quoted) - )) - if dbcol.max: - metrics.append(M( - metric_name='max__' + dbcol.column_name, - verbose_name='max__' + dbcol.column_name, - metric_type='max', - expression="MAX({})".format(quoted) - )) - if dbcol.min: - metrics.append(M( - metric_name='min__' + dbcol.column_name, - verbose_name='min__' + dbcol.column_name, - metric_type='min', - expression="MIN({})".format(quoted) - )) - if dbcol.count_distinct: - metrics.append(M( - metric_name='count_distinct__' + dbcol.column_name, - verbose_name='count_distinct__' + dbcol.column_name, - metric_type='count_distinct', - expression="COUNT(DISTINCT {})".format(quoted) - )) - dbcol.type = datatype - db.session.merge(self) - db.session.commit() - - metrics.append(M( - metric_name='count', - verbose_name='COUNT(*)', - metric_type='count', - expression="COUNT(*)" - )) - for metric in metrics: - m = ( - db.session.query(M) - .filter(M.metric_name == metric.metric_name) - .filter(M.table_id == self.id) - .first() - ) - metric.table_id = self.id - if not m: - db.session.add(metric) - db.session.commit() - if not self.main_dttm_col: - self.main_dttm_col = any_date_col - - -class SqlMetric(Model, AuditMixinNullable): - - """ORM object for metrics, each table can have multiple metrics""" - - __tablename__ = 'sql_metrics' - id = Column(Integer, primary_key=True) - metric_name = Column(String(512)) - verbose_name = Column(String(1024)) - metric_type = Column(String(32)) - table_id = Column(Integer, ForeignKey('tables.id')) - table = relationship( - 'SqlaTable', backref='metrics', foreign_keys=[table_id]) - expression = Column(Text) - description = Column(Text) - - -class TableColumn(Model, AuditMixinNullable): - - """ORM object for table columns, each table can have multiple columns""" - - __tablename__ = 'table_columns' - id = Column(Integer, primary_key=True) - table_id = Column(Integer, ForeignKey('tables.id')) - table = relationship( - 'SqlaTable', backref='columns', foreign_keys=[table_id]) - column_name = Column(String(256)) - is_dttm = Column(Boolean, default=False) - is_active = Column(Boolean, default=True) - type = Column(String(32), default='') - groupby = Column(Boolean, default=False) - count_distinct = Column(Boolean, default=False) - sum = Column(Boolean, default=False) - max = Column(Boolean, default=False) - min = Column(Boolean, default=False) - filterable = Column(Boolean, default=False) - expression = Column(Text, default='') - description = Column(Text, default='') - - def __repr__(self): - return self.column_name - - @property - def isnum(self): - return self.type in ('LONG', 'DOUBLE', 'FLOAT') - - -class DruidCluster(Model, AuditMixinNullable): - - """ORM object referencing the Druid clusters""" - - __tablename__ = 'clusters' - id = Column(Integer, primary_key=True) - cluster_name = Column(String(250), unique=True) - coordinator_host = Column(String(256)) - coordinator_port = Column(Integer) - coordinator_endpoint = Column( - String(256), default='druid/coordinator/v1/metadata') - broker_host = Column(String(256)) - broker_port = Column(Integer) - broker_endpoint = Column(String(256), default='druid/v2') - metadata_last_refreshed = Column(DateTime) - - def __repr__(self): - return self.cluster_name - - def get_pydruid_client(self): - cli = client.PyDruid( - "http://{0}:{1}/".format(self.broker_host, self.broker_port), - self.broker_endpoint) - return cli - - def refresh_datasources(self): - endpoint = ( - "http://{self.coordinator_host}:{self.coordinator_port}/" - "{self.coordinator_endpoint}/datasources" - ).format(self=self) - - datasources = json.loads(requests.get(endpoint).text) - for datasource in datasources: - DruidDatasource.sync_to_db(datasource, self) - - -class DruidDatasource(Model, AuditMixinNullable, Queryable): - - """ORM object referencing Druid datasources (tables)""" - - type = "druid" - - baselink = "datasourcemodelview" - - __tablename__ = 'datasources' - id = Column(Integer, primary_key=True) - datasource_name = Column(String(250), unique=True) - is_featured = Column(Boolean, default=False) - is_hidden = Column(Boolean, default=False) - description = Column(Text) - default_endpoint = Column(Text) - user_id = Column(Integer, ForeignKey('ab_user.id')) - owner = relationship('User', backref='datasources', foreign_keys=[user_id]) - cluster_name = Column( - String(250), ForeignKey('clusters.cluster_name')) - cluster = relationship( - 'DruidCluster', backref='datasources', foreign_keys=[cluster_name]) - offset = Column(Integer, default=0) - - @property - def metrics_combo(self): - return sorted( - [(m.metric_name, m.verbose_name) for m in self.metrics], - key=lambda x: x[1]) - - @property - def name(self): - return self.datasource_name - - @property - def perm(self): - return ( - "[{self.cluster_name}].[{self.datasource_name}]" - "(id:{self.id})").format(self=self) - - @property - def url(self): - return '/datasourcemodelview/edit/{}'.format(self.id) - - @property - def link(self): - return ( - '' - '{self.datasource_name}').format(**locals()) - - @property - def full_name(self): - return ( - "[{self.cluster_name}]." - "[{self.datasource_name}]").format(self=self) - - def __repr__(self): - return self.datasource_name - - @property - def datasource_link(self): - url = "/panoramix/explore/{self.type}/{self.id}/".format(self=self) - return '{self.datasource_name}'.format( - url=url, self=self) - - def get_metric_obj(self, metric_name): - return [ - m.json_obj for m in self.metrics - if m.metric_name == metric_name - ][0] - - def latest_metadata(self): - """Returns segment metadata from the latest segment""" - client = self.cluster.get_pydruid_client() - results = client.time_boundary(datasource=self.datasource_name) - if not results: - return - max_time = results[0]['result']['maxTime'] - max_time = parse(max_time) - intervals = (max_time - timedelta(seconds=1)).isoformat() + '/' - intervals += (max_time + timedelta(seconds=1)).isoformat() - segment_metadata = client.segment_metadata( - datasource=self.datasource_name, - intervals=intervals) - if segment_metadata: - return segment_metadata[-1]['columns'] - - def generate_metrics(self): - for col in self.columns: - col.generate_metrics() - - @classmethod - def sync_to_db(cls, name, cluster): - """Fetches metadata for that datasource and merges the Panoramix db""" - print("Syncing Druid datasource [{}]".format(name)) - session = get_session() - datasource = session.query(cls).filter_by(datasource_name=name).first() - if not datasource: - datasource = cls(datasource_name=name) - session.add(datasource) - flash("Adding new datasource [{}]".format(name), "success") - else: - flash("Refreshing datasource [{}]".format(name), "info") - datasource.cluster = cluster - - cols = datasource.latest_metadata() - if not cols: - return - for col in cols: - col_obj = ( - session - .query(DruidColumn) - .filter_by(datasource_name=name, column_name=col) - .first() - ) - datatype = cols[col]['type'] - if not col_obj: - col_obj = DruidColumn(datasource_name=name, column_name=col) - session.add(col_obj) - if datatype == "STRING": - col_obj.groupby = True - col_obj.filterable = True - if col_obj: - col_obj.type = cols[col]['type'] - col_obj.datasource = datasource - col_obj.generate_metrics() - - def query( - self, groupby, metrics, - granularity, - from_dttm, to_dttm, - filter=None, # noqa - is_timeseries=True, - timeseries_limit=None, - row_limit=None, - inner_from_dttm=None, inner_to_dttm=None, - extras=None, # noqa - select=None): - """Runs a query against Druid and returns a dataframe. - - This query interface is common to SqlAlchemy and Druid - """ - # TODO refactor into using a TBD Query object - qry_start_dttm = datetime.now() - - inner_from_dttm = inner_from_dttm or from_dttm - inner_to_dttm = inner_to_dttm or to_dttm - - # add tzinfo to native datetime with config - from_dttm = from_dttm.replace(tzinfo=config.get("DRUID_TZ")) - to_dttm = to_dttm.replace(tzinfo=config.get("DRUID_TZ")) - - query_str = "" - aggregations = { - m.metric_name: m.json_obj - for m in self.metrics if m.metric_name in metrics - } - granularity = granularity or "all" - if granularity != "all": - granularity = utils.parse_human_timedelta( - granularity).total_seconds() * 1000 - if not isinstance(granularity, string_types): - granularity = {"type": "duration", "duration": granularity} - - qry = dict( - datasource=self.datasource_name, - dimensions=groupby, - aggregations=aggregations, - granularity=granularity, - intervals=from_dttm.isoformat() + '/' + to_dttm.isoformat(), - ) - filters = None - for col, op, eq in filter: - cond = None - if op == '==': - cond = Dimension(col) == eq - elif op == '!=': - cond = ~(Dimension(col) == eq) - elif op in ('in', 'not in'): - fields = [] - splitted = eq.split(',') - if len(splitted) > 1: - for s in eq.split(','): - s = s.strip() - fields.append(Filter.build_filter(Dimension(col) == s)) - cond = Filter(type="or", fields=fields) - else: - cond = Dimension(col) == eq - if op == 'not in': - cond = ~cond - if filters: - filters = Filter(type="and", fields=[ - Filter.build_filter(cond), - Filter.build_filter(filters) - ]) - else: - filters = cond - - if filters: - qry['filter'] = filters - - client = self.cluster.get_pydruid_client() - orig_filters = filters - if timeseries_limit and is_timeseries: - # Limit on the number of timeseries, doing a two-phases query - pre_qry = deepcopy(qry) - pre_qry['granularity'] = "all" - pre_qry['limit_spec'] = { - "type": "default", - "limit": timeseries_limit, - 'intervals': ( - inner_from_dttm.isoformat() + '/' + - inner_to_dttm.isoformat()), - "columns": [{ - "dimension": metrics[0] if metrics else self.metrics[0], - "direction": "descending", - }], - } - client.groupby(**pre_qry) - query_str += "// Two phase query\n// Phase 1\n" - query_str += json.dumps(client.query_dict, indent=2) + "\n" - query_str += "//\nPhase 2 (built based on phase one's results)\n" - df = client.export_pandas() - if df is not None and not df.empty: - dims = qry['dimensions'] - filters = [] - for _, row in df.iterrows(): - fields = [] - for dim in dims: - f = Filter.build_filter(Dimension(dim) == row[dim]) - fields.append(f) - if len(fields) > 1: - filt = Filter(type="and", fields=fields) - filters.append(Filter.build_filter(filt)) - elif fields: - filters.append(fields[0]) - - if filters: - ff = Filter(type="or", fields=filters) - if not orig_filters: - qry['filter'] = ff - else: - qry['filter'] = Filter(type="and", fields=[ - Filter.build_filter(ff), - Filter.build_filter(orig_filters)]) - qry['limit_spec'] = None - if row_limit: - qry['limit_spec'] = { - "type": "default", - "limit": row_limit, - "columns": [{ - "dimension": metrics[0] if metrics else self.metrics[0], - "direction": "descending", - }], - } - client.groupby(**qry) - query_str += json.dumps(client.query_dict, indent=2) - df = client.export_pandas() - if df is None or df.size == 0: - raise Exception("No data was returned.") - - if ( - not is_timeseries and - granularity == "all" and - 'timestamp' in df.columns): - del df['timestamp'] - - # Reordering columns - cols = [] - if 'timestamp' in df.columns: - cols += ['timestamp'] - cols += [col for col in groupby if col in df.columns] - cols += [col for col in metrics if col in df.columns] - cols += [col for col in df.columns if col not in cols] - df = df[cols] - return QueryResult( - df=df, - query=query_str, - duration=datetime.now() - qry_start_dttm) - - -class Log(Model): - - """ORM object used to log Panoramix actions to the database""" - - __tablename__ = 'logs' - - id = Column(Integer, primary_key=True) - action = Column(String(512)) - user_id = Column(Integer, ForeignKey('ab_user.id')) - dashboard_id = Column(Integer) - slice_id = Column(Integer) - user_id = Column(Integer, ForeignKey('ab_user.id')) - json = Column(Text) - user = relationship('User', backref='logs', foreign_keys=[user_id]) - dttm = Column(DateTime, default=func.now()) - - @classmethod - def log_this(cls, f): - """Decorator to log user actions""" - @functools.wraps(f) - def wrapper(*args, **kwargs): - user_id = None - if g.user: - user_id = g.user.id - d = request.args.to_dict() - d.update(kwargs) - log = cls( - action=f.__name__, - json=json.dumps(d), - dashboard_id=d.get('dashboard_id') or None, - slice_id=d.get('slice_id') or None, - user_id=user_id) - db.session.add(log) - db.session.commit() - return f(*args, **kwargs) - return wrapper - - - - -class DruidMetric(Model): - - """ORM object referencing Druid metrics for a datasource""" - - __tablename__ = 'metrics' - id = Column(Integer, primary_key=True) - metric_name = Column(String(512)) - verbose_name = Column(String(1024)) - metric_type = Column(String(32)) - datasource_name = Column( - String(250), - ForeignKey('datasources.datasource_name')) - datasource = relationship('DruidDatasource', backref='metrics') - json = Column(Text) - description = Column(Text) - - @property - def json_obj(self): - try: - obj = json.loads(self.json) - except Exception: - obj = {} - return obj - - -class DruidColumn(Model): - - """ORM model for storing Druid datasource column metadata""" - - __tablename__ = 'columns' - id = Column(Integer, primary_key=True) - datasource_name = Column( - String(250), - ForeignKey('datasources.datasource_name')) - datasource = relationship('DruidDatasource', backref='columns') - column_name = Column(String(256)) - is_active = Column(Boolean, default=True) - type = Column(String(32)) - groupby = Column(Boolean, default=False) - count_distinct = Column(Boolean, default=False) - sum = Column(Boolean, default=False) - max = Column(Boolean, default=False) - min = Column(Boolean, default=False) - filterable = Column(Boolean, default=False) - description = Column(Text) - - def __repr__(self): - return self.column_name - - @property - def isnum(self): - return self.type in ('LONG', 'DOUBLE', 'FLOAT') - - def generate_metrics(self): - """Generate metrics based on the column metadata""" - M = DruidMetric - metrics = [] - metrics.append(DruidMetric( - metric_name='count', - verbose_name='COUNT(*)', - metric_type='count', - json=json.dumps({'type': 'count', 'name': 'count'}) - )) - # Somehow we need to reassign this for UDAFs - if self.type in ('DOUBLE', 'FLOAT'): - corrected_type = 'DOUBLE' - else: - corrected_type = self.type - - if self.sum and self.isnum: - mt = corrected_type.lower() + 'Sum' - name = 'sum__' + self.column_name - metrics.append(DruidMetric( - metric_name=name, - metric_type='sum', - verbose_name='SUM({})'.format(self.column_name), - json=json.dumps({ - 'type': mt, 'name': name, 'fieldName': self.column_name}) - )) - if self.min and self.isnum: - mt = corrected_type.lower() + 'Min' - name = 'min__' + self.column_name - metrics.append(DruidMetric( - metric_name=name, - metric_type='min', - verbose_name='MIN({})'.format(self.column_name), - json=json.dumps({ - 'type': mt, 'name': name, 'fieldName': self.column_name}) - )) - if self.max and self.isnum: - mt = corrected_type.lower() + 'Max' - name = 'max__' + self.column_name - metrics.append(DruidMetric( - metric_name=name, - metric_type='max', - verbose_name='MAX({})'.format(self.column_name), - json=json.dumps({ - 'type': mt, 'name': name, 'fieldName': self.column_name}) - )) - if self.count_distinct: - mt = 'count_distinct' - name = 'count_distinct__' + self.column_name - metrics.append(DruidMetric( - metric_name=name, - verbose_name='COUNT(DISTINCT {})'.format(self.column_name), - metric_type='count_distinct', - json=json.dumps({ - 'type': 'cardinality', - 'name': name, - 'fieldNames': [self.column_name]}) - )) - session = get_session() - for metric in metrics: - m = ( - session.query(M) - .filter(M.metric_name == metric.metric_name) - .filter(M.datasource_name == self.datasource_name) - .filter(DruidCluster.cluster_name == self.datasource.cluster_name) - .first() - ) - metric.datasource_name = self.datasource_name - if not m: - session.add(metric) - session.commit() diff --git a/panoramix/static/assets b/panoramix/static/assets deleted file mode 120000 index ec2e4be2f839..000000000000 --- a/panoramix/static/assets +++ /dev/null @@ -1 +0,0 @@ -../assets \ No newline at end of file diff --git a/panoramix/static/docs b/panoramix/static/docs deleted file mode 120000 index 932170420302..000000000000 --- a/panoramix/static/docs +++ /dev/null @@ -1 +0,0 @@ -../../docs/_build/html/ \ No newline at end of file diff --git a/panoramix/static/favicon.png b/panoramix/static/favicon.png deleted file mode 100644 index 50c8c9a45830..000000000000 Binary files a/panoramix/static/favicon.png and /dev/null differ diff --git a/panoramix/static/img/bubble.png b/panoramix/static/img/bubble.png deleted file mode 100644 index a65d5ed8b804..000000000000 Binary files a/panoramix/static/img/bubble.png and /dev/null differ diff --git a/panoramix/static/img/cardash.jpg b/panoramix/static/img/cardash.jpg deleted file mode 100644 index e8dbcc49cc8e..000000000000 Binary files a/panoramix/static/img/cardash.jpg and /dev/null differ diff --git a/panoramix/static/img/cloud.png b/panoramix/static/img/cloud.png deleted file mode 100644 index 9478806cfaf8..000000000000 Binary files a/panoramix/static/img/cloud.png and /dev/null differ diff --git a/panoramix/static/img/dash.png b/panoramix/static/img/dash.png deleted file mode 100644 index 83ecf8e57aa6..000000000000 Binary files a/panoramix/static/img/dash.png and /dev/null differ diff --git a/panoramix/static/img/favicon.png b/panoramix/static/img/favicon.png deleted file mode 100644 index 35fc3c161fd7..000000000000 Binary files a/panoramix/static/img/favicon.png and /dev/null differ diff --git a/panoramix/static/img/gallery.jpg b/panoramix/static/img/gallery.jpg deleted file mode 100644 index 42ebad239e9a..000000000000 Binary files a/panoramix/static/img/gallery.jpg and /dev/null differ diff --git a/panoramix/static/img/loading.gif b/panoramix/static/img/loading.gif deleted file mode 100644 index 01ae3939c49b..000000000000 Binary files a/panoramix/static/img/loading.gif and /dev/null differ diff --git a/panoramix/static/img/panoramix_screenshot.png b/panoramix/static/img/panoramix_screenshot.png deleted file mode 100644 index 804576455340..000000000000 Binary files a/panoramix/static/img/panoramix_screenshot.png and /dev/null differ diff --git a/panoramix/static/img/penguins.png b/panoramix/static/img/penguins.png deleted file mode 100644 index 14bfc440e414..000000000000 Binary files a/panoramix/static/img/penguins.png and /dev/null differ diff --git a/panoramix/static/img/serpe.jpg b/panoramix/static/img/serpe.jpg deleted file mode 100644 index 79a91666a567..000000000000 Binary files a/panoramix/static/img/serpe.jpg and /dev/null differ diff --git a/panoramix/static/img/servers.jpg b/panoramix/static/img/servers.jpg deleted file mode 100644 index 2d4604b6bdb5..000000000000 Binary files a/panoramix/static/img/servers.jpg and /dev/null differ diff --git a/panoramix/static/img/slice.jpg b/panoramix/static/img/slice.jpg deleted file mode 100644 index 68c53c4e8e77..000000000000 Binary files a/panoramix/static/img/slice.jpg and /dev/null differ diff --git a/panoramix/templates/appbuilder/baselayout.html b/panoramix/templates/appbuilder/baselayout.html deleted file mode 100644 index 33110ce16408..000000000000 --- a/panoramix/templates/appbuilder/baselayout.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends 'appbuilder/init.html' %} -{% import 'appbuilder/baselib.html' as baselib %} - -{% block body %} - {% include 'appbuilder/general/confirm.html' %} - {% include 'appbuilder/general/alert.html' %} - - {% block navbar %} -
- {% include 'appbuilder/navbar.html' %} -
- {% endblock %} - - {% block uncontained %}{% endblock %} - -
-
- {% block messages %} - {% include 'appbuilder/flash.html' %} - {% endblock %} - {% block content %} - {% endblock %} -
-
-
- {% block content_fluid %} - {% endblock %} -
- - {% block footer %} -
- -
- {% endblock %} -{% endblock %} - diff --git a/panoramix/templates/appbuilder/general/widgets/list.html b/panoramix/templates/appbuilder/general/widgets/list.html deleted file mode 100644 index aae0580c72ee..000000000000 --- a/panoramix/templates/appbuilder/general/widgets/list.html +++ /dev/null @@ -1,81 +0,0 @@ -{% import 'appbuilder/general/lib.html' as lib %} -{% extends 'appbuilder/general/widgets/base_list.html' %} - - - {% block begin_content scoped %} -
- - {% endblock %} - - {% block begin_loop_header scoped %} - - - {% if actions %} - - {% endif %} - - {% if can_show or can_edit or can_delete %} - - {% endif %} - - {% for item in include_columns %} - {% if item in order_columns %} - {% set res = item | get_link_order(modelview_name) %} - {% if res == 2 %} - - {% elif res == 1 %} - - {% else %} - - {% endif %} - {% else %} - - {% endif %} - {% endfor %} - - - {% endblock %} - - {% block begin_loop_values %} - {% for item in value_columns %} - {% set pk = pks[loop.index-1] %} - - {% if actions %} - - {% endif %} - {% if can_show or can_edit or can_delete %} - - {% endif %} - {% for value in include_columns %} - - {% endfor %} - - {% endfor %} - {% endblock %} - - {% block end_content scoped %} -
- - {{label_columns.get(item)}} - {{label_columns.get(item)}} - {{label_columns.get(item)}} - {{label_columns.get(item)}}
- -
- {{ lib.btn_crud(can_show, can_edit, can_delete, pk, modelview_name, filters) }} -
- {% if item[value].__class__.__name__ == 'bool' %} - - {% else %} - {{ item[value]|safe }} - {% endif %} -
-
- {% endblock %} - diff --git a/panoramix/templates/appbuilder/navbar.html b/panoramix/templates/appbuilder/navbar.html deleted file mode 100644 index d0d260c2c238..000000000000 --- a/panoramix/templates/appbuilder/navbar.html +++ /dev/null @@ -1,35 +0,0 @@ - - - -{% set menu = appbuilder.menu %} -{% set languages = appbuilder.languages %} - - diff --git a/panoramix/templates/appbuilder/navbar_right.html b/panoramix/templates/appbuilder/navbar_right.html deleted file mode 100644 index 433bf1bf48db..000000000000 --- a/panoramix/templates/appbuilder/navbar_right.html +++ /dev/null @@ -1,43 +0,0 @@ - -{% macro locale_menu(languages) %} -{% set locale = session['locale'] %} -{% if not locale %} - {% set locale = 'en' %} -{% endif %} - -{% endmacro %} - - - -{% if not current_user.is_anonymous() %} - -{% else %} -
  • - {{_("Login")}}
  • -{% endif %} diff --git a/panoramix/templates/index.html b/panoramix/templates/index.html deleted file mode 100644 index ab2339fe91e1..000000000000 --- a/panoramix/templates/index.html +++ /dev/null @@ -1,116 +0,0 @@ -{% extends "appbuilder/base.html" %} - -{% block uncontained %} -
    - -
    -
    -
    -
    - -

    Dashboards

    -

    Browse the dashboards list

    -

    - -

    -
    -
    - -

    Slices

    -

    "Slices" are individual views into a single dataset

    -

    - -

    -
    -
    - Generic placeholder image -

    Gallery

    -

    Navigate through the growing set of visualizations

    -

    - -

    -
    -
    -
    -
    -{% endblock %} - -{% block tail_js %} -{{ super() }} - -{% endblock %} diff --git a/panoramix/templates/panoramix/ajah.html b/panoramix/templates/panoramix/ajah.html deleted file mode 100644 index b5d122680d71..000000000000 --- a/panoramix/templates/panoramix/ajah.html +++ /dev/null @@ -1 +0,0 @@ -{{ content |safe }} diff --git a/panoramix/templates/panoramix/base.html b/panoramix/templates/panoramix/base.html deleted file mode 100644 index b075d52be60d..000000000000 --- a/panoramix/templates/panoramix/base.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "appbuilder/baselayout.html" %} - - {% block head_css %} - {{super()}} - - - {% endblock %} - - {% block head_js %} - {{super()}} - - {% endblock %} diff --git a/panoramix/templates/panoramix/basic.html b/panoramix/templates/panoramix/basic.html deleted file mode 100644 index 8d0cba178ab4..000000000000 --- a/panoramix/templates/panoramix/basic.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - {% block title %} - {% if appbuilder and appbuilder.app_name %} {{ appbuilder.app_name }} {% endif %} - {% endblock %} - - {% block head_meta %}{% endblock %} - {% block head_css %} - - - {% endblock %} - {% block head_js %} - - {% endblock %} - - - - {% block navbar %} - {% if not viz or viz.request.args.get("standalone") != "true" %} -
    - {% include 'appbuilder/navbar.html' %} -
    - {% endif %} - {% endblock %} - - {% block body %} -
    - Oops! React.js is not working properly. -
    - {% endblock %} - - {% block tail_js %} - {% endblock %} - - diff --git a/panoramix/templates/panoramix/dashboard.html b/panoramix/templates/panoramix/dashboard.html deleted file mode 100644 index 27b20026677d..000000000000 --- a/panoramix/templates/panoramix/dashboard.html +++ /dev/null @@ -1,139 +0,0 @@ -{% extends "panoramix/basic.html" %} - -{% block head_js %} - {{ super() }} - -{% endblock %} - -{% block body %} -
    - - - - -
    -
    -
    -
    -

    - {{ dashboard.dashboard_title }} -

    -
    -
    -
    - - - - - - -
    -
    -
    -
    - -
    -{% endblock %} diff --git a/panoramix/templates/panoramix/explore.html b/panoramix/templates/panoramix/explore.html deleted file mode 100644 index db6763faf5ad..000000000000 --- a/panoramix/templates/panoramix/explore.html +++ /dev/null @@ -1,212 +0,0 @@ -{% extends "panoramix/basic.html" %} - -{% block body %} - {% set datasource = viz.datasource %} - {% set form = viz.form %} - - {% macro panofield(fieldname)%} -
    - {% set field = form.get_field(fieldname)%} -
    - {{ viz.get_form_override(fieldname, 'label') or field.label }} - {% if field.description %} - - {% endif %} - {{ field(class_=form.field_css_classes(field.name)) }} -
    -
    - {% endmacro %} - -
    - -
    -{% endblock %} - -{% block tail_js %} - {{ super() }} - -{% endblock %} diff --git a/panoramix/templates/panoramix/featured.html b/panoramix/templates/panoramix/featured.html deleted file mode 100644 index 36e37a9e7b69..000000000000 --- a/panoramix/templates/panoramix/featured.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "panoramix/basic.html" %} - -{% block head_js %} - {{ super() }} - -{% endblock %} - -{% block body %} -
    -
    -

    Featured Datasets

    -
    -
    - - - - - - - - - - - {% for dataset in featured_datasets %} - - - - - - - {% endfor %} - - -
    -
    -{% endblock %} - diff --git a/panoramix/templates/panoramix/index.html b/panoramix/templates/panoramix/index.html deleted file mode 100644 index 678f630949c0..000000000000 --- a/panoramix/templates/panoramix/index.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "panoramix/basic.html" %} - -{% block tail_js %} - {{ super() }} - -{% endblock %} diff --git a/panoramix/templates/panoramix/models/database/add.html b/panoramix/templates/panoramix/models/database/add.html deleted file mode 100644 index eede9ae0ac6d..000000000000 --- a/panoramix/templates/panoramix/models/database/add.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "appbuilder/general/model/add.html" %} - -{% import "panoramix/models/database/macros.html" as macros %} -{% block tail_js %} - {{ super() }} - {{ macros.testconn() }} -{% endblock %} diff --git a/panoramix/templates/panoramix/models/database/edit.html b/panoramix/templates/panoramix/models/database/edit.html deleted file mode 100644 index c32517bee7cb..000000000000 --- a/panoramix/templates/panoramix/models/database/edit.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "appbuilder/general/model/edit.html" %} - -{% import "panoramix/models/database/macros.html" as macros %} -{% block tail_js %} - {{ super() }} - {{ macros.testconn() }} -{% endblock %} diff --git a/panoramix/templates/panoramix/models/database/macros.html b/panoramix/templates/panoramix/models/database/macros.html deleted file mode 100644 index 398355ee2b01..000000000000 --- a/panoramix/templates/panoramix/models/database/macros.html +++ /dev/null @@ -1,29 +0,0 @@ -{% macro testconn() %} - -{% endmacro %} diff --git a/panoramix/templates/panoramix/no_data.html b/panoramix/templates/panoramix/no_data.html deleted file mode 100644 index d2e30a6d803f..000000000000 --- a/panoramix/templates/panoramix/no_data.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "panoramix/datasource.html" %} - -{% block viz %} -No data: review your incantations. -{% endblock %} diff --git a/panoramix/templates/panoramix/sql.html b/panoramix/templates/panoramix/sql.html deleted file mode 100644 index c4ba542a0f5f..000000000000 --- a/panoramix/templates/panoramix/sql.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends "panoramix/basic.html" %} - -{% block head_css %} - {{super()}} - - - -{% endblock %} - -{% block body %} -
    - -
    -{% endblock %} - -{% block tail_js %} -{{ super() }} - -{% endblock %} diff --git a/panoramix/templates/panoramix/standalone.html b/panoramix/templates/panoramix/standalone.html deleted file mode 100644 index 5a0818d221de..000000000000 --- a/panoramix/templates/panoramix/standalone.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - {% set CSS_THEME = appbuilder.get_app.config.get("CSS_THEME") %} - {% set height = request.args.get("height", 700) %} - {% if CSS_THEME %} - - {% endif %} - - -
    - loading -
    -
    - - diff --git a/panoramix/templates/panoramix/traceback.html b/panoramix/templates/panoramix/traceback.html deleted file mode 100644 index 97fc6b4961cf..000000000000 --- a/panoramix/templates/panoramix/traceback.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -
    {{ art }}
    -{{ title }}
    -{{ error_msg }}
    -
    -
    - - diff --git a/panoramix/templates/panoramix/viz.html b/panoramix/templates/panoramix/viz.html deleted file mode 100644 index ad801731eeb9..000000000000 --- a/panoramix/templates/panoramix/viz.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if viz.form_data.get("json") == "true" %} - {{ viz.get_json() }} -{% else %} - - {% if viz.request.args.get("standalone") == "true" %} - {% extends 'panoramix/standalone.html' %} - {% else %} - {% extends 'panoramix/explore.html' %} - {% endif %} - -{% endif %} diff --git a/panoramix/utils.py b/panoramix/utils.py deleted file mode 100644 index f642be29c2e2..000000000000 --- a/panoramix/utils.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Utility functions used across Panoramix""" - -from datetime import datetime -import hashlib -import functools -import json -import logging - -from dateutil.parser import parse -from sqlalchemy.types import TypeDecorator, TEXT -from markdown import markdown as md -import parsedatetime -from flask_appbuilder.security.sqla import models as ab_models - - -class memoized(object): - - """Decorator that caches a function's return value each time it is called. - If called later with the same arguments, the cached value is returned, and - not re-evaluated. - """ - - def __init__(self, func): - self.func = func - self.cache = {} - def __call__(self, *args): - try: - return self.cache[args] - except KeyError: - value = self.func(*args) - self.cache[args] = value - return value - except TypeError: - # uncachable -- for instance, passing a list as an argument. - # Better to not cache than to blow up entirely. - return self.func(*args) - def __repr__(self): - """Return the function's docstring.""" - return self.func.__doc__ - def __get__(self, obj, objtype): - """Support instance methods.""" - return functools.partial(self.__call__, obj) - -def list_minus(l, minus): - """Returns l without what is in minus - - >>> list_minus([1, 2, 3], [2]) - [1, 3] - """ - return [o for o in l if o not in minus] - -def parse_human_datetime(s): - """ - Returns ``datetime.datetime`` from human readable strings - - >>> from datetime import date, timedelta - >>> from dateutil.relativedelta import relativedelta - >>> parse_human_datetime('2015-04-03') - datetime.datetime(2015, 4, 3, 0, 0) - >>> parse_human_datetime('2/3/1969') - datetime.datetime(1969, 2, 3, 0, 0) - >>> parse_human_datetime("now") <= datetime.now() - True - >>> parse_human_datetime("yesterday") <= datetime.now() - True - >>> date.today() - timedelta(1) == parse_human_datetime('yesterday').date() - True - >>> year_ago_1 = parse_human_datetime('one year ago').date() - >>> year_ago_2 = (datetime.now() - relativedelta(years=1) ).date() - >>> year_ago_1 == year_ago_2 - True - """ - try: - dttm = parse(s) - except Exception: - try: - cal = parsedatetime.Calendar() - dttm = dttm_from_timtuple(cal.parse(s)[0]) - except Exception as e: - logging.exception(e) - raise ValueError("Couldn't parse date string [{}]".format(s)) - return dttm - - -def dttm_from_timtuple(d): - return datetime( - d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec) - - -def merge_perm(sm, permission_name, view_menu_name): - pv = sm.find_permission_view_menu(permission_name, view_menu_name) - if not pv: - sm.add_permission_view_menu(permission_name, view_menu_name) - - -def parse_human_timedelta(s): - """ - Returns ``datetime.datetime`` from natural language time deltas - - >>> parse_human_datetime("now") <= datetime.now() - True - """ - cal = parsedatetime.Calendar() - dttm = dttm_from_timtuple(datetime.now().timetuple()) - d = cal.parse(s, dttm)[0] - d = datetime( - d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec) - return d - dttm - - -class JSONEncodedDict(TypeDecorator): - - """Represents an immutable structure as a json-encoded string.""" - - impl = TEXT - def process_bind_param(self, value, dialect): - if value is not None: - value = json.dumps(value) - - return value - - def process_result_value(self, value, dialect): - if value is not None: - value = json.loads(value) - return value - - -class ColorFactory(object): - - """Used to generated arrays of colors server side""" - - BNB_COLORS = [ - #rausch hackb kazan babu lima beach barol - '#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c', - '#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a', - '#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e', - ] - - def __init__(self, hash_based=False): - self.d = {} - self.hash_based = hash_based - - def get(self, s): - """Gets a color from a string and memoize the association - - >>> cf = ColorFactory() - >>> cf.get('item_1') - '#ff5a5f' - >>> cf.get('item_2') - '#7b0051' - >>> cf.get('item_1') - '#ff5a5f' - """ - if self.hash_based: - s = s.encode('utf-8') - h = hashlib.md5(s) - i = int(h.hexdigest(), 16) - else: - if s not in self.d: - self.d[s] = len(self.d) - i = self.d[s] - return self.BNB_COLORS[i % len(self.BNB_COLORS)] - - -def init(panoramix): - """Inits the Panoramix application with security roles and such""" - db = panoramix.db - models = panoramix.models - sm = panoramix.appbuilder.sm - alpha = sm.add_role("Alpha") - admin = sm.add_role("Admin") - - merge_perm(sm, 'all_datasource_access', 'all_datasource_access') - - perms = db.session.query(ab_models.PermissionView).all() - for perm in perms: - if perm.permission.name == 'datasource_access': - continue - if perm.view_menu.name not in ( - 'UserDBModelView', 'RoleModelView', 'ResetPasswordView', - 'Security'): - sm.add_permission_role(alpha, perm) - sm.add_permission_role(admin, perm) - gamma = sm.add_role("Gamma") - for perm in perms: - if( - perm.view_menu.name not in ( - 'ResetPasswordView', - 'RoleModelView', - 'UserDBModelView', - 'Security') and - perm.permission.name not in ( - 'all_datasource_access', - 'can_add', - 'can_download', - 'can_delete', - 'can_edit', - 'can_save', - 'datasource_access', - 'muldelete', - )): - sm.add_permission_role(gamma, perm) - session = db.session() - table_perms = [ - table.perm for table in session.query(models.SqlaTable).all()] - table_perms += [ - table.perm for table in session.query(models.DruidDatasource).all()] - for table_perm in table_perms: - merge_perm(sm, 'datasource_access', table_perm) - - -def datetime_f(dttm): - """Formats datetime to take less room when it is recent""" - if dttm: - dttm = dttm.isoformat() - now_iso = datetime.now().isoformat() - if now_iso[:10] == dttm[:10]: - dttm = dttm[11:] - elif now_iso[:4] == dttm[:4]: - dttm = dttm[5:] - return "{}".format(dttm) - - -def json_iso_dttm_ser(obj): - """ - json serializer that deals with dates - - >>> dttm = datetime(1970, 1, 1) - >>> json.dumps({'dttm': dttm}, default=json_iso_dttm_ser) - '{"dttm": "1970-01-01T00:00:00"}' - """ - if isinstance(obj, datetime): - obj = obj.isoformat() - return obj - - -def markdown(s): - s = s or '' - return md(s, [ - 'markdown.extensions.tables', - 'markdown.extensions.fenced_code', - 'markdown.extensions.codehilite',]) - - -def readfile(filepath): - with open(filepath) as f: - content = f.read() - return content diff --git a/panoramix/views.py b/panoramix/views.py deleted file mode 100644 index 6e62d7fca5b2..000000000000 --- a/panoramix/views.py +++ /dev/null @@ -1,774 +0,0 @@ -"""Flask web views for Panoramix""" - -from datetime import datetime -import json -import logging -import re -import traceback - -from flask import request, redirect, flash, Response, render_template, Markup -from flask.ext.appbuilder import ModelView, CompactCRUDMixin, BaseView, expose -from flask.ext.appbuilder.actions import action -from flask.ext.appbuilder.models.sqla.interface import SQLAInterface -from flask.ext.appbuilder.security.decorators import has_access -from pydruid.client import doublesum -from sqlalchemy import create_engine -import sqlalchemy as sqla -from wtforms.validators import ValidationError -import pandas as pd -from sqlalchemy import select, text -from sqlalchemy.sql.expression import TextAsFrom - -from panoramix import appbuilder, db, models, viz, utils, app, sm, ascii_art - -config = app.config -log_this = models.Log.log_this - - -def validate_json(form, field): # noqa - try: - json.loads(field.data) - except Exception as e: - logging.exception(e) - raise ValidationError("json isn't valid") - - -class DeleteMixin(object): - @action( - "muldelete", "Delete", "Delete all Really?", "fa-trash", single=False) - def muldelete(self, items): - self.datamodel.delete_all(items) - self.update_redirect() - return redirect(self.get_redirect()) - - -class PanoramixModelView(ModelView): - page_size = 500 - - -class TableColumnInlineView(CompactCRUDMixin, PanoramixModelView): # noqa - datamodel = SQLAInterface(models.TableColumn) - can_delete = False - edit_columns = [ - 'column_name', 'description', 'groupby', 'filterable', 'table', - 'count_distinct', 'sum', 'min', 'max', 'expression', 'is_dttm'] - add_columns = edit_columns - list_columns = [ - 'column_name', 'type', 'groupby', 'filterable', 'count_distinct', - 'sum', 'min', 'max', 'is_dttm'] - page_size = 500 - description_columns = { - 'is_dttm': ( - "Whether to make this column available as a " - "[Time Granularity] option, column has to be DATETIME or " - "DATETIME-like"), - } -appbuilder.add_view_no_menu(TableColumnInlineView) - -appbuilder.add_link( - "Featured Datasets", - href='/panoramix/featured', - category='Sources', - category_icon='fa-table', - icon="fa-star") - -appbuilder.add_separator("Sources") - - -class DruidColumnInlineView(CompactCRUDMixin, PanoramixModelView): # noqa - datamodel = SQLAInterface(models.DruidColumn) - edit_columns = [ - 'column_name', 'description', 'datasource', 'groupby', - 'count_distinct', 'sum', 'min', 'max'] - list_columns = [ - 'column_name', 'type', 'groupby', 'filterable', 'count_distinct', - 'sum', 'min', 'max'] - can_delete = False - page_size = 500 - - def post_update(self, col): - col.generate_metrics() - -appbuilder.add_view_no_menu(DruidColumnInlineView) - - -class SqlMetricInlineView(CompactCRUDMixin, PanoramixModelView): # noqa - datamodel = SQLAInterface(models.SqlMetric) - list_columns = ['metric_name', 'verbose_name', 'metric_type'] - edit_columns = [ - 'metric_name', 'description', 'verbose_name', 'metric_type', - 'expression', 'table'] - add_columns = edit_columns - page_size = 500 -appbuilder.add_view_no_menu(SqlMetricInlineView) - - -class DruidMetricInlineView(CompactCRUDMixin, PanoramixModelView): # noqa - datamodel = SQLAInterface(models.DruidMetric) - list_columns = ['metric_name', 'verbose_name', 'metric_type'] - edit_columns = [ - 'metric_name', 'description', 'verbose_name', 'metric_type', - 'datasource', 'json'] - add_columns = [ - 'metric_name', 'verbose_name', 'metric_type', 'datasource', 'json'] - page_size = 500 - validators_columns = { - 'json': [validate_json], - } -appbuilder.add_view_no_menu(DruidMetricInlineView) - - -class DatabaseView(PanoramixModelView, DeleteMixin): # noqa - datamodel = SQLAInterface(models.Database) - list_columns = ['database_name', 'sql_link', 'created_by_', 'changed_on'] - order_columns = utils.list_minus(list_columns, ['created_by_']) - add_columns = ['database_name', 'sqlalchemy_uri'] - search_exclude_columns = ('password',) - edit_columns = add_columns - add_template = "panoramix/models/database/add.html" - edit_template = "panoramix/models/database/edit.html" - base_order = ('changed_on','desc') - description_columns = { - 'sqlalchemy_uri': ( - "Refer to the SqlAlchemy docs for more information on how " - "to structure your URI here: " - "http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html") - } - def pre_add(self, db): - conn = sqla.engine.url.make_url(db.sqlalchemy_uri) - db.password = conn.password - conn.password = "X" * 10 if conn.password else None - db.sqlalchemy_uri = str(conn) # hides the password - - def pre_update(self, db): - self.pre_add(db) - - -appbuilder.add_view( - DatabaseView, - "Databases", - icon="fa-database", - category="Sources", - category_icon='fa-database',) - - -class TableModelView(PanoramixModelView, DeleteMixin): # noqa - datamodel = SQLAInterface(models.SqlaTable) - list_columns = [ - 'table_link', 'database', 'sql_link', 'is_featured', - 'changed_by_', 'changed_on'] - add_columns = ['table_name', 'database', 'default_endpoint', 'offset'] - edit_columns = [ - 'table_name', 'is_featured', 'database', 'description', 'owner', - 'main_dttm_col', 'default_endpoint', 'offset'] - related_views = [TableColumnInlineView, SqlMetricInlineView] - base_order = ('changed_on','desc') - description_columns = { - 'offset': "Timezone offset (in hours) for this datasource", - 'description': Markup( - "Supports " - "markdown"), - } - - def post_add(self, table): - try: - table.fetch_metadata() - except Exception as e: - logging.exception(e) - flash( - "Table [{}] doesn't seem to exist, " - "couldn't fetch metadata".format(table.table_name), - "danger") - utils.merge_perm(sm, 'datasource_access', table.perm) - - def post_update(self, table): - self.post_add(table) - -appbuilder.add_view( - TableModelView, - "Tables", - category="Sources", - icon='fa-table',) - - -appbuilder.add_separator("Sources") - - -class DruidClusterModelView(PanoramixModelView, DeleteMixin): # noqa - datamodel = SQLAInterface(models.DruidCluster) - add_columns = [ - 'cluster_name', - 'coordinator_host', 'coordinator_port', 'coordinator_endpoint', - 'broker_host', 'broker_port', 'broker_endpoint', - ] - edit_columns = add_columns - list_columns = ['cluster_name', 'metadata_last_refreshed'] - -appbuilder.add_view( - DruidClusterModelView, - "Druid Clusters", - icon="fa-cubes", - category="Sources", - category_icon='fa-database',) - - -class SliceModelView(PanoramixModelView, DeleteMixin): # noqa - datamodel = SQLAInterface(models.Slice) - can_add = False - list_columns = [ - 'slice_link', 'viz_type', - 'datasource_link', 'created_by_', 'changed_on'] - order_columns = utils.list_minus(list_columns, ['created_by_']) - edit_columns = [ - 'slice_name', 'description', 'viz_type', 'druid_datasource', - 'table', 'dashboards', 'params'] - base_order = ('changed_on','desc') - description_columns = { - 'description': Markup( - "The content here can be displayed as widget headers in the " - "dashboard view. Supports " - "" - "markdown"), - } - - -appbuilder.add_view( - SliceModelView, - "Slices", - icon="fa-bar-chart", - category="", - category_icon='',) - - -class DashboardModelView(PanoramixModelView, DeleteMixin): # noqa - datamodel = SQLAInterface(models.Dashboard) - list_columns = ['dashboard_link', 'created_by_', 'changed_on'] - order_columns = utils.list_minus(list_columns, ['created_by_']) - edit_columns = [ - 'dashboard_title', 'slug', 'slices', 'position_json', 'css', - 'json_metadata'] - add_columns = edit_columns - base_order = ('changed_on','desc') - description_columns = { - 'position_json': ( - "This json object describes the positioning of the widgets in " - "the dashboard. It is dynamically generated when adjusting " - "the widgets size and positions by using drag & drop in " - "the dashboard view"), - 'css': ( - "The css for individual dashboards can be altered here, or " - "in the dashboard view where changes are immediately " - "visible"), - 'slug': "To get a readable URL for your dashboard", - } - def pre_add(self, obj): - obj.slug = obj.slug.strip() or None - if obj.slug: - obj.slug = obj.slug.replace(" ", "-") - obj.slug = re.sub(r'\W+', '', obj.slug) - - def pre_update(self, obj): - self.pre_add(obj) - - -appbuilder.add_view( - DashboardModelView, - "Dashboards", - icon="fa-dashboard", - category="", - category_icon='',) - - -class LogModelView(PanoramixModelView): - datamodel = SQLAInterface(models.Log) - list_columns = ('user', 'action', 'dttm') - edit_columns = ('user', 'action', 'dttm', 'json') - base_order = ('dttm','desc') - -appbuilder.add_view( - LogModelView, - "Action Log", - category="Security", - icon="fa-list-ol") - - -class DruidDatasourceModelView(PanoramixModelView, DeleteMixin): # noqa - datamodel = SQLAInterface(models.DruidDatasource) - list_columns = [ - 'datasource_link', 'cluster', 'owner', - 'created_by_', 'created_on', - 'changed_by_', 'changed_on', - 'offset'] - order_columns = utils.list_minus( - list_columns, ['created_by_', 'changed_by_']) - related_views = [DruidColumnInlineView, DruidMetricInlineView] - edit_columns = [ - 'datasource_name', 'cluster', 'description', 'owner', - 'is_featured', 'is_hidden', 'default_endpoint', 'offset'] - page_size = 500 - base_order = ('datasource_name', 'asc') - description_columns = { - 'offset': "Timezone offset (in hours) for this datasource", - 'description': Markup( - "Supports markdown"), - } - - def post_add(self, datasource): - datasource.generate_metrics() - utils.merge_perm(sm, 'datasource_access', datasource.perm) - - def post_update(self, datasource): - self.post_add(datasource) - -appbuilder.add_view( - DruidDatasourceModelView, - "Druid Datasources", - category="Sources", - icon="fa-cube") - - -@app.route('/health') -def health(): - return "OK" - - -@app.route('/ping') -def ping(): - return "OK" - - -class R(BaseView): - - @log_this - @expose("/") - def index(self, url_id): - url = db.session.query(models.Url).filter_by(id=url_id).first() - if url: - print(url.url) - return redirect('/' + url.url) - else: - flash("URL to nowhere...", "danger") - return redirect('/') - - @log_this - @expose("/shortner/", methods=['POST', 'GET']) - def shortner(self): - url = request.form.get('data') - obj = models.Url(url=url) - db.session.add(obj) - db.session.commit() - return("{request.headers[Host]}/r/{obj.id}".format( - request=request, obj=obj)) - -appbuilder.add_view_no_menu(R) - - -class Panoramix(BaseView): - - """The base views for Panoramix!""" - - @has_access - @expose("/explore///") - @expose("/datasource///") # Legacy url - @log_this - def explore(self, datasource_type, datasource_id): - if datasource_type == "table": - datasource = ( - db.session - .query(models.SqlaTable) - .filter_by(id=datasource_id) - .first() - ) - else: - datasource = ( - db.session - .query(models.DruidDatasource) - .filter_by(id=datasource_id) - .first() - ) - - all_datasource_access = self.appbuilder.sm.has_access( - 'all_datasource_access', 'all_datasource_access') - datasource_access = self.appbuilder.sm.has_access( - 'datasource_access', datasource.perm) - if not (all_datasource_access or datasource_access): - flash( - "You don't seem to have access to this datasource", - "danger") - return redirect('/slicemodelview/list/') - action = request.args.get('action') - if action in ('save', 'overwrite'): - session = db.session() - - # TODO use form processing form wtforms - d = request.args.to_dict(flat=False) - del d['action'] - del d['previous_viz_type'] - as_list = ('metrics', 'groupby', 'columns') - for k in d: - v = d.get(k) - if k in as_list and not isinstance(v, list): - d[k] = [v] if v else [] - if k not in as_list and isinstance(v, list): - d[k] = v[0] - - table_id = druid_datasource_id = None - datasource_type = request.args.get('datasource_type') - if datasource_type in ('datasource', 'druid'): - druid_datasource_id = request.args.get('datasource_id') - elif datasource_type == 'table': - table_id = request.args.get('datasource_id') - - slice_name = request.args.get('slice_name') - - if action == "save": - slc = models.Slice() - msg = "Slice [{}] has been saved".format(slice_name) - elif action == "overwrite": - slc = ( - session.query(models.Slice) - .filter_by(id=request.args.get("slice_id")) - .first() - ) - msg = "Slice [{}] has been overwritten".format(slice_name) - - slc.params = json.dumps(d, indent=4, sort_keys=True) - slc.datasource_name = request.args.get('datasource_name') - slc.viz_type = request.args.get('viz_type') - slc.druid_datasource_id = druid_datasource_id - slc.table_id = table_id - slc.datasource_type = datasource_type - slc.slice_name = slice_name - - session.merge(slc) - session.commit() - flash(msg, "info") - return redirect(slc.slice_url) - - - if not datasource: - flash("The datasource seem to have been deleted", "alert") - viz_type = request.args.get("viz_type") - if not viz_type and datasource.default_endpoint: - return redirect(datasource.default_endpoint) - if not viz_type: - viz_type = "table" - obj = viz.viz_types[viz_type]( - datasource, - form_data=request.args) - if request.args.get("csv") == "true": - status = 200 - payload = obj.get_csv() - return Response( - payload, - status=status, - mimetype="application/csv") - - slice_id = request.args.get("slice_id") - slc = None - if slice_id: - slc = ( - db.session.query(models.Slice) - .filter_by(id=request.args.get("slice_id")) - .first() - ) - if request.args.get("json") == "true": - status = 200 - if config.get("DEBUG"): - payload = obj.get_json() - else: - try: - payload = obj.get_json() - except Exception as e: - logging.exception(e) - payload = str(e) - status = 500 - return Response( - payload, - status=status, - mimetype="application/json") - else: - if config.get("DEBUG"): - resp = self.render_template( - "panoramix/viz.html", viz=obj, slice=slc) - try: - resp = self.render_template( - "panoramix/viz.html", viz=obj, slice=slc) - except Exception as e: - if config.get("DEBUG"): - raise(e) - return Response( - str(e), - status=500, - mimetype="application/json") - return resp - - @has_access - @expose("/checkbox////", methods=['GET']) - def checkbox(self, model_view, id_, attr, value): - """endpoint for checking/unchecking any boolean in a sqla model""" - model = None - if model_view == 'TableColumnInlineView': - model = models.TableColumn - elif model_view == 'DruidColumnInlineView': - model = models.DruidColumn - - obj = db.session.query(model).filter_by(id=id_).first() - if obj: - setattr(obj, attr, value=='true') - db.session.commit() - return Response("OK", mimetype="application/json") - - - @has_access - @expose("/save_dash//", methods=['GET', 'POST']) - def save_dash(self, dashboard_id): - """Save a dashboard's metadata""" - data = json.loads(request.form.get('data')) - positions = data['positions'] - slice_ids = [int(d['slice_id']) for d in positions] - session = db.session() - Dash = models.Dashboard - dash = session.query(Dash).filter_by(id=dashboard_id).first() - dash.slices = [o for o in dash.slices if o.id in slice_ids] - dash.position_json = json.dumps(data['positions'], indent=4) - md = dash.metadata_dejson - if 'filter_immune_slices' not in md: - md['filter_immune_slices'] = [] - md['expanded_slices'] = data['expanded_slices'] - dash.json_metadata = json.dumps(md, indent=4) - dash.css = data['css'] - session.merge(dash) - session.commit() - session.close() - return "SUCCESS" - - @has_access - @expose("/testconn", methods=["POST", "GET"]) - def testconn(self): - """Tests a sqla connection""" - try: - uri = request.form.get('uri') - engine = create_engine(uri) - engine.connect() - return json.dumps(engine.table_names(), indent=4) - except Exception: - return Response( - traceback.format_exc(), - status=500, - mimetype="application/json") - - @has_access - @expose("/dashboard//") - def dashboard(self, dashboard_id): - """Server side rendering for a dashboard""" - session = db.session() - qry = session.query(models.Dashboard) - if dashboard_id.isdigit(): - qry = qry.filter_by(id=int(dashboard_id)) - else: - qry = qry.filter_by(slug=dashboard_id) - - templates = session.query(models.CssTemplate).all() - - dash = qry.first() - - # Hack to log the dashboard_id properly, even when getting a slug - @log_this - def dashboard(**kwargs): # noqa - pass - dashboard(dashboard_id=dash.id) - - pos_dict = {} - if dash.position_json: - pos_dict = { - int(o['slice_id']):o - for o in json.loads(dash.position_json)} - return self.render_template( - "panoramix/dashboard.html", dashboard=dash, - templates=templates, - pos_dict=pos_dict) - - @has_access - @expose("/sql//") - @log_this - def sql(self, database_id): - mydb = db.session.query( - models.Database).filter_by(id=database_id).first() - engine = mydb.get_sqla_engine() - tables = engine.table_names() - - table_name=request.args.get('table_name') - return self.render_template( - "panoramix/sql.html", - tables=tables, - table_name=table_name, - db=mydb) - - @has_access - @expose("/table///") - @log_this - def table(self, database_id, table_name): - mydb = db.session.query( - models.Database).filter_by(id=database_id).first() - cols = mydb.get_columns(table_name) - df = pd.DataFrame([(c['name'], c['type']) for c in cols]) - df.columns = ['col', 'type'] - return self.render_template( - "panoramix/ajah.html", - content=df.to_html( - index=False, - na_rep='', - classes=( - "dataframe table table-striped table-bordered " - "table-condensed sql_results"))) - - @has_access - @expose("/select_star///") - @log_this - def select_star(self, database_id, table_name): - mydb = db.session.query( - models.Database).filter_by(id=database_id).first() - t = mydb.get_table(table_name) - fields = ", ".join( - [c.name for c in t.columns] or "*") - s = "SELECT\n{}\nFROM {}".format(fields, table_name) - return self.render_template( - "panoramix/ajah.html", - content=s - ) - - @has_access - @expose("/runsql/", methods=['POST', 'GET']) - @log_this - def runsql(self): - session = db.session() - limit = 1000 - data = json.loads(request.form.get('data')) - sql = data.get('sql') - database_id = data.get('database_id') - mydb = session.query(models.Database).filter_by(id=database_id).first() - content = "" - if mydb: - eng = mydb.get_sqla_engine() - if limit: - sql = sql.strip().strip(';') - qry = ( - select('*') - .select_from(TextAsFrom(text(sql), ['*']).alias('inner_qry')) - .limit(limit) - ) - sql= str(qry.compile(eng, compile_kwargs={"literal_binds": True})) - try: - df = pd.read_sql_query(sql=sql, con=eng) - content = df.to_html( - index=False, - na_rep='', - classes=( - "dataframe table table-striped table-bordered " - "table-condensed sql_results")) - except Exception as e: - content = ( - '
    ' - "{}
    " - ).format(e.message) - session.commit() - return content - - @has_access - @expose("/refresh_datasources/") - def refresh_datasources(self): - session = db.session() - for cluster in session.query(models.DruidCluster).all(): - try: - cluster.refresh_datasources() - except Exception as e: - flash( - "Error while processing cluster '{}'\n{}".format( - cluster, str(e)), - "danger") - logging.exception(e) - return redirect('/druidclustermodelview/list/') - cluster.metadata_last_refreshed = datetime.now() - flash( - "Refreshed metadata from cluster " - "[" + cluster.cluster_name + "]", - 'info') - session.commit() - return redirect("/datasourcemodelview/list/") - - @expose("/autocomplete///") - def autocomplete(self, datasource, column): - client = utils.get_pydruid_client() - top = client.topn( - datasource=datasource, - granularity='all', - intervals='2013-10-04/2020-10-10', - aggregations={"count": doublesum("count")}, - dimension=column, - metric='count', - threshold=1000, - ) - values = sorted([d[column] for d in top[0]['result']]) - return json.dumps(values) - - @app.errorhandler(500) - def show_traceback(self): - if config.get("SHOW_STACKTRACE"): - error_msg = traceback.format_exc() - else: - error_msg = "FATAL ERROR\n" - error_msg = ( - "Stacktrace is hidden. Change the SHOW_STACKTRACE " - "configuration setting to enable it") - return render_template( - 'panoramix/traceback.html', - error_msg=error_msg, - title=ascii_art.stacktrace, - art=ascii_art.error), 500 - - @has_access - @expose("/featured", methods=['GET']) - def featured(self): - session = db.session() - datasets_sqla = ( - session.query(models.SqlaTable) - .filter_by(is_featured=True) - .all() - ) - datasets_druid = ( - session.query(models.DruidDatasource) - .filter_by(is_featured=True) - .all() - ) - featured_datasets = datasets_sqla + datasets_druid - return self.render_template( - 'panoramix/featured.html', - featured_datasets=featured_datasets, - utils=utils) - -appbuilder.add_view_no_menu(Panoramix) -appbuilder.add_link( - "Refresh Druid Metadata", - href='/panoramix/refresh_datasources/', - category='Sources', - category_icon='fa-database', - icon="fa-cog") - - -class CssTemplateModelView(PanoramixModelView, DeleteMixin): - datamodel = SQLAInterface(models.CssTemplate) - list_columns = ['template_name'] - edit_columns = ['template_name', 'css'] - add_columns = edit_columns - -appbuilder.add_separator("Sources") -appbuilder.add_view( - CssTemplateModelView, - "CSS Templates", - icon="fa-css3", - category="Sources", - category_icon='',) - - diff --git a/panoramix/viz.py b/panoramix/viz.py deleted file mode 100644 index 83cd0c8a593b..000000000000 --- a/panoramix/viz.py +++ /dev/null @@ -1,1228 +0,0 @@ -""" -This module contains the "Viz" objects that represent the backend of all -the visualizations that Panoramix can render -""" - -from collections import OrderedDict, defaultdict -from datetime import datetime, timedelta -import json -import uuid - -from flask import flash, request, Markup -from markdown import markdown -from pandas.io.json import dumps -from werkzeug.datastructures import ImmutableMultiDict -from werkzeug.urls import Href -import pandas as pd - -from panoramix import app, utils -from panoramix.forms import FormFactory - -from six import string_types - -config = app.config - - -class BaseViz(object): - - """All visualizations derive this base class""" - - viz_type = None - verbose_name = "Base Viz" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'metrics', 'groupby', - ) - },) - form_overrides = {} - - def __init__(self, datasource, form_data): - self.orig_form_data = form_data - self.datasource = datasource - self.request = request - self.viz_type = form_data.get("viz_type") - - # TODO refactor all form related logic out of here and into forms.py - ff = FormFactory(self) - form_class = ff.get_form() - defaults = form_class().data.copy() - previous_viz_type = form_data.get('previous_viz_type') - if isinstance(form_data, ImmutableMultiDict): - form = form_class(form_data) - else: - form = form_class(**form_data) - data = form.data.copy() - - if not form.validate(): - for k, v in form.errors.items(): - if not data.get('json') and not data.get('async'): - flash("{}: {}".format(k, " ".join(v)), 'danger') - if previous_viz_type != self.viz_type: - data = { - k: form.data[k] - for k in form_data.keys() - if k in form.data} - defaults.update(data) - self.form_data = defaults - self.query = "" - - self.form_data['previous_viz_type'] = self.viz_type - self.token = self.form_data.get( - 'token', 'token_' + uuid.uuid4().hex[:8]) - - self.metrics = self.form_data.get('metrics') or [] - self.groupby = self.form_data.get('groupby') or [] - self.reassignments() - - def get_form_override(self, fieldname, attr): - if ( - fieldname in self.form_overrides and - attr in self.form_overrides[fieldname]): - s = self.form_overrides[fieldname][attr] - if attr == 'label': - s = ''.format(**locals()) - s = Markup(s) - return s - - @classmethod - def flat_form_fields(cls): - l = set() - for d in cls.fieldsets: - for obj in d['fields']: - if obj and isinstance(obj, (tuple, list)): - l |= {a for a in obj if a} - elif obj: - l.add(obj) - return tuple(l) - - def reassignments(self): - pass - - def get_url(self, **kwargs): - d = self.orig_form_data.copy() - if 'json' in d: - del d['json'] - if 'action' in d: - del d['action'] - d.update(kwargs) - # Remove unchecked checkboxes because HTML is weird like that - for key in d.keys(): - if d[key] is False: - del d[key] - href = Href( - '/panoramix/explore/{self.datasource.type}/' - '{self.datasource.id}/'.format(**locals())) - return href(d) - - def get_df(self, query_obj=None): - """Returns a pandas dataframe based on the query object""" - if not query_obj: - query_obj = self.query_obj() - - self.error_msg = "" - self.results = None - - # The datasource here can be different backend but the interface is common - self.results = self.datasource.query(**query_obj) - self.query = self.results.query - df = self.results.df - if df is None or df.empty: - raise Exception("No data, review your incantations!") - else: - if 'timestamp' in df.columns: - df.timestamp = pd.to_datetime(df.timestamp, utc=False) - if self.datasource.offset: - df.timestamp += timedelta(hours=self.datasource.offset) - df = df.fillna(0) - return df - - @property - def form(self): - return self.form_class(**self.form_data) - - @property - def form_class(self): - return FormFactory(self).get_form() - - def query_filters(self): - """Processes the filters for the query""" - form_data = self.form_data - # Building filters - filters = [] - for i in range(1, 10): - col = form_data.get("flt_col_" + str(i)) - op = form_data.get("flt_op_" + str(i)) - eq = form_data.get("flt_eq_" + str(i)) - if col and op and eq: - filters.append((col, op, eq)) - - # Extra filters (coming from dashboard) - extra_filters = form_data.get('extra_filters') - if extra_filters: - extra_filters = json.loads(extra_filters) - for slice_filters in extra_filters.values(): - for col, vals in slice_filters.items(): - if col and vals: - filters += [(col, 'in', ",".join(vals))] - return filters - - def query_obj(self): - """Building a query object""" - form_data = self.form_data - groupby = form_data.get("groupby") or [] - metrics = form_data.get("metrics") or ['count'] - granularity = \ - form_data.get("granularity") or form_data.get("granularity_sqla") - limit = int(form_data.get("limit", 0)) - row_limit = int( - form_data.get("row_limit", config.get("ROW_LIMIT"))) - since = form_data.get("since", "1 year ago") - from_dttm = utils.parse_human_datetime(since) - if from_dttm > datetime.now(): - from_dttm = datetime.now() - (from_dttm-datetime.now()) - until = form_data.get("until", "now") - to_dttm = utils.parse_human_datetime(until) - if from_dttm > to_dttm: - flash("The date range doesn't seem right.", "danger") - from_dttm = to_dttm # Making them identical to not raise - - # extras are used to query elements specific to a datasource type - # for instance the extra where clause that applies only to Tables - extras = { - 'where': form_data.get("where", ''), - 'having': form_data.get("having", ''), - 'time_grain_sqla': form_data.get("time_grain_sqla", ''), - } - d = { - 'granularity': granularity, - 'from_dttm': from_dttm, - 'to_dttm': to_dttm, - 'is_timeseries': self.is_timeseries, - 'groupby': groupby, - 'metrics': metrics, - 'row_limit': row_limit, - 'filter': self.query_filters(), - 'timeseries_limit': limit, - 'extras': extras, - } - return d - - def get_json(self): - payload = { - 'data': json.loads(self.get_json_data()), - 'query': self.query, - 'form_data': self.form_data, - 'json_endpoint': self.json_endpoint, - 'csv_endpoint': self.csv_endpoint, - 'standalone_endpoint': self.standalone_endpoint, - } - return json.dumps(payload) - - def get_csv(self): - df = self.get_df() - return df.to_csv(index=False) - - def get_json_data(self): - return json.dumps([]) - - @property - def json_endpoint(self): - return self.get_url(json="true") - - @property - def csv_endpoint(self): - return self.get_url(csv="true") - - @property - def standalone_endpoint(self): - return self.get_url(standalone="true") - - @property - def data(self): - content = { - 'viz_name': self.viz_type, - 'json_endpoint': self.json_endpoint, - 'csv_endpoint': self.csv_endpoint, - 'standalone_endpoint': self.standalone_endpoint, - 'token': self.token, - 'form_data': self.form_data, - } - return content - - @property - def json_data(self): - return dumps(self.data) - -class TableViz(BaseViz): - viz_type = "table" - verbose_name = "Table View" - fieldsets = ( - { - 'label': "Chart Options", - 'fields': ( - 'row_limit', - ('include_search', None), - ) - }, - { - 'label': "GROUP BY", - 'fields': ( - 'groupby', - 'metrics', - ) - }, - { - 'label': "NOT GROUPED BY", - 'fields': ( - 'all_columns', - ) - },) - is_timeseries = False - - - def query_obj(self): - d = super(TableViz, self).query_obj() - fd = self.form_data - if fd.get('all_columns') and (fd.get('groupby') or fd.get('metrics')): - raise Exception( - "Choose either fields to [Group By] and [Metrics] or " - "[Columns], not both") - if fd.get('all_columns'): - d['columns'] = fd.get('all_columns') - d['groupby'] = [] - return d - - def get_df(self, query_obj=None): - df = super(TableViz, self).get_df(query_obj) - if ( - self.form_data.get("granularity") == "all" and - 'timestamp' in df): - del df['timestamp'] - return df - - def get_json_data(self): - df = self.get_df() - return json.dumps( - dict( - records=df.to_dict(orient="records"), - columns=list(df.columns), - ), - default=utils.json_iso_dttm_ser, - ) - - -class PivotTableViz(BaseViz): - viz_type = "pivot_table" - verbose_name = "Pivot Table" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'groupby', - 'columns', - 'metrics', - 'pandas_aggfunc', - ) - },) - - def query_obj(self): - d = super(PivotTableViz, self).query_obj() - groupby = self.form_data.get('groupby') - columns = self.form_data.get('columns') - metrics = self.form_data.get('metrics') - if not columns: - columns = [] - if not groupby: - groupby = [] - if not groupby: - raise Exception("Please choose at least one \"Group by\" field ") - if not metrics: - raise Exception("Please choose at least one metric") - if ( - any(v in groupby for v in columns) or - any(v in columns for v in groupby)): - raise Exception("groupby and columns can't overlap") - - d['groupby'] = list(set(groupby) | set(columns)) - return d - - def get_df(self, query_obj=None): - df = super(PivotTableViz, self).get_df(query_obj) - if ( - self.form_data.get("granularity") == "all" and - 'timestamp' in df): - del df['timestamp'] - df = df.pivot_table( - index=self.form_data.get('groupby'), - columns=self.form_data.get('columns'), - values=self.form_data.get('metrics'), - aggfunc=self.form_data.get('pandas_aggfunc'), - margins=True, - ) - return df - - def get_json_data(self): - return dumps(self.get_df().to_html( - na_rep='', - classes=( - "dataframe table table-striped table-bordered " - "table-condensed table-hover"))) - - -class MarkupViz(BaseViz): - viz_type = "markup" - verbose_name = "Markup Widget" - fieldsets = ( - { - 'label': None, - 'fields': ('markup_type', 'code') - },) - is_timeseries = False - - def rendered(self): - markup_type = self.form_data.get("markup_type") - code = self.form_data.get("code", '') - if markup_type == "markdown": - return markdown(code) - elif markup_type == "html": - return code - - def get_json_data(self): - return dumps(dict(html=self.rendered())) - - -class WordCloudViz(BaseViz): - - """Integration with the nice library at: - - https://github.com/jasondavies/d3-cloud - """ - - viz_type = "word_cloud" - verbose_name = "Word Cloud" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'series', 'metric', 'limit', - ('size_from', 'size_to'), - 'rotation', - ) - },) - - def query_obj(self): - d = super(WordCloudViz, self).query_obj() - - d['metrics'] = [self.form_data.get('metric')] - d['groupby'] = [self.form_data.get('series')] - return d - - def get_json_data(self): - df = self.get_df() - # Ordering the columns - df = df[[self.form_data.get('series'), self.form_data.get('metric')]] - # Labeling the columns for uniform json schema - df.columns = ['text', 'size'] - return df.to_json(orient="records") - - -class NVD3Viz(BaseViz): - - """Base class for all nvd3 vizs""" - - viz_type = None - verbose_name = "Base NVD3 Viz" - is_timeseries = False - - -class BubbleViz(NVD3Viz): - - """Based on the NVD3 bubble chart""" - - viz_type = "bubble" - verbose_name = "Bubble Chart" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'series', 'entity', - 'x', 'y', - 'size', 'limit', - ) - }, - { - 'label': 'Chart Options', - 'fields': ( - ('x_log_scale', 'y_log_scale'), - ('show_legend', None), - 'max_bubble_size', - ) - },) - - def query_obj(self): - form_data = self.form_data - d = super(BubbleViz, self).query_obj() - d['groupby'] = list({ - form_data.get('series'), - form_data.get('entity') - }) - self.x_metric = form_data.get('x') - self.y_metric = form_data.get('y') - self.z_metric = form_data.get('size') - self.entity = form_data.get('entity') - self.series = form_data.get('series') - - d['metrics'] = [ - self.z_metric, - self.x_metric, - self.y_metric, - ] - if not all(d['metrics'] + [self.entity, self.series]): - raise Exception("Pick a metric for x, y and size") - return d - - def get_df(self, query_obj=None): - df = super(BubbleViz, self).get_df(query_obj) - df = df.fillna(0) - df['x'] = df[[self.x_metric]] - df['y'] = df[[self.y_metric]] - df['size'] = df[[self.z_metric]] - df['shape'] = 'circle' - df['group'] = df[[self.series]] - return df - - def get_json_data(self): - df = self.get_df() - series = defaultdict(list) - for row in df.to_dict(orient='records'): - series[row['group']].append(row) - chart_data = [] - for k, v in series.items(): - chart_data.append({ - 'key': k, - 'values': v }) - return dumps(chart_data) - -class BigNumberViz(BaseViz): - viz_type = "big_number" - verbose_name = "Big Number" - is_timeseries = True - fieldsets = ( - { - 'label': None, - 'fields': ( - 'metric', - 'compare_lag', - 'compare_suffix', - 'y_axis_format', - ) - },) - form_overrides = { - 'y_axis_format': { - 'label': 'Number format', - } - } - - def reassignments(self): - metric = self.form_data.get('metric') - if not metric: - self.form_data['metric'] = self.orig_form_data.get('metrics') - - - def query_obj(self): - d = super(BigNumberViz, self).query_obj() - metric = self.form_data.get('metric') - if not metric: - raise Exception("Pick a metric!") - d['metrics'] = [self.form_data.get('metric')] - self.form_data['metric'] = metric - return d - - def get_json_data(self): - form_data = self.form_data - df = self.get_df() - df = df.sort(columns=df.columns[0]) - compare_lag = form_data.get("compare_lag", "") - compare_lag = int(compare_lag) if compare_lag and compare_lag.isdigit() else 0 - d = { - 'data': df.values.tolist(), - 'compare_lag': compare_lag, - 'compare_suffix': form_data.get('compare_suffix', ''), - } - return dumps(d) - - -class NVD3TimeSeriesViz(NVD3Viz): - viz_type = "line" - verbose_name = "Time Series - Line Chart" - sort_series = False - is_timeseries = True - fieldsets = ( - { - 'label': None, - 'fields': ( - 'metrics', - 'groupby', 'limit', - ), - }, { - 'label': 'Chart Options', - 'fields': ( - ('show_brush', 'show_legend'), - ('rich_tooltip', 'y_axis_zero'), - ('y_log_scale', 'contribution'), - ('x_axis_format', 'y_axis_format'), - ('line_interpolation', 'x_axis_showminmax'), - ), - }, { - 'label': 'Advanced Analytics', - 'description': ( - "This section contains options " - "that allow for advanced analytical post processing " - "of query results"), - 'fields': ( - ('rolling_type', 'rolling_periods'), - 'time_compare', - 'num_period_compare', - None, - ('resample_how', 'resample_rule',), 'resample_fillmethod' - ), - }, - ) - - def get_df(self, query_obj=None): - form_data = self.form_data - df = super(NVD3TimeSeriesViz, self).get_df(query_obj) - - df = df.fillna(0) - if form_data.get("granularity") == "all": - raise Exception("Pick a time granularity for your time series") - - df = df.pivot_table( - index="timestamp", - columns=form_data.get('groupby'), - values=form_data.get('metrics')) - - fm = form_data.get("resample_fillmethod") - if not fm: - fm = None - how = form_data.get("resample_how") - rule = form_data.get("resample_rule") - if how and rule: - df = df.resample(rule, how=how, fill_method=fm) - if not fm: - df = df.fillna(0) - - - if self.sort_series: - dfs = df.sum() - dfs.sort(ascending=False) - df = df[dfs.index] - - if form_data.get("contribution"): - dft = df.T - df = (dft / dft.sum()).T - - num_period_compare = form_data.get("num_period_compare") - if num_period_compare: - num_period_compare = int(num_period_compare) - df = (df / df.shift(num_period_compare)) - 1 - df = df[num_period_compare:] - - rolling_periods = form_data.get("rolling_periods") - rolling_type = form_data.get("rolling_type") - - if rolling_type in ('mean', 'std', 'sum') and rolling_periods: - if rolling_type == 'mean': - df = pd.rolling_mean(df, int(rolling_periods), min_periods=0) - elif rolling_type == 'std': - df = pd.rolling_std(df, int(rolling_periods), min_periods=0) - elif rolling_type == 'sum': - df = pd.rolling_sum(df, int(rolling_periods), min_periods=0) - elif rolling_type == 'cumsum': - df = df.cumsum() - return df - - def to_series(self, df, classed='', title_suffix=''): - series = df.to_dict('series') - - chart_data = [] - for name in df.T.index.tolist(): - ys = series[name] - if df[name].dtype.kind not in "biufc": - continue - df['timestamp'] = pd.to_datetime(df.index, utc=False) - if isinstance(name, string_types): - series_title = name - else: - name = ["{}".format(s) for s in name] - if len(self.form_data.get('metrics')) > 1: - series_title = ", ".join(name) - else: - series_title = ", ".join(name[1:]) - if title_suffix: - series_title += title_suffix - - d = { - "key": series_title, - "classed": classed, - "values": [{'x': ds, 'y': ys[ds]} for ds in df.timestamp], - } - chart_data.append(d) - return chart_data - - def get_json_data(self): - df = self.get_df() - chart_data = self.to_series(df) - - time_compare = self.form_data.get('time_compare') - if time_compare: - query_object = self.query_obj() - delta = utils.parse_human_timedelta(time_compare) - query_object['inner_from_dttm'] = query_object['from_dttm'] - query_object['inner_to_dttm'] = query_object['to_dttm'] - query_object['from_dttm'] -= delta - query_object['to_dttm'] -= delta - - df2 = self.get_df(query_object) - df2.index += delta - chart_data += self.to_series( - df2, classed='dashed', title_suffix="---") - chart_data = sorted(chart_data, key=lambda x: x['key']) - return dumps(chart_data) - - -class NVD3TimeSeriesBarViz(NVD3TimeSeriesViz): - viz_type = "bar" - sort_series = True - verbose_name = "Time Series - Bar Chart" - fieldsets = [NVD3TimeSeriesViz.fieldsets[0]] + [{ - 'label': 'Chart Options', - 'fields': ( - ('show_brush', 'show_legend'), - ('rich_tooltip', 'y_axis_zero'), - ('y_log_scale', 'contribution'), - ('x_axis_format', 'y_axis_format'), - ('line_interpolation', 'bar_stacked'), - ('x_axis_showminmax', None), - ), }] + [NVD3TimeSeriesViz.fieldsets[2]] - - -class NVD3CompareTimeSeriesViz(NVD3TimeSeriesViz): - viz_type = 'compare' - verbose_name = "Time Series - Percent Change" - - -class NVD3TimeSeriesStackedViz(NVD3TimeSeriesViz): - viz_type = "area" - verbose_name = "Time Series - Stacked" - sort_series = True - fieldsets = [NVD3TimeSeriesViz.fieldsets[0]] + [{ - 'label': 'Chart Options', - 'fields': ( - ('show_brush', 'show_legend'), - ('rich_tooltip', 'y_axis_zero'), - ('y_log_scale', 'contribution'), - ('x_axis_format', 'y_axis_format'), - ('x_axis_showminmax'), - ('line_interpolation', 'stacked_style'), - ), }] + [NVD3TimeSeriesViz.fieldsets[2]] - - -class DistributionPieViz(NVD3Viz): - viz_type = "pie" - verbose_name = "Distribution - NVD3 - Pie Chart" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'metrics', 'groupby', - 'limit', - ('donut', 'show_legend'), - ) - },) - - def query_obj(self): - d = super(DistributionPieViz, self).query_obj() - d['is_timeseries'] = False - return d - - def get_df(self, query_obj=None): - df = super(DistributionPieViz, self).get_df(query_obj) - df = df.pivot_table( - index=self.groupby, - values=[self.metrics[0]]) - df = df.sort(self.metrics[0], ascending=False) - return df - - def get_json_data(self): - df = self.get_df() - df = df.reset_index() - df.columns = ['x', 'y'] - return dumps(df.to_dict(orient="records")) - - -class DistributionBarViz(DistributionPieViz): - viz_type = "dist_bar" - verbose_name = "Distribution - Bar Chart" - is_timeseries = False - fieldsets = ( - { - 'label': 'Chart Options', - 'fields': ( - 'groupby', - 'columns', - 'metrics', - 'row_limit', - ('show_legend', 'bar_stacked'), - ) - },) - form_overrides = { - 'groupby': { - 'label': 'Series', - }, - 'columns': { - 'label': 'Breakdowns', - 'description': "Defines how each series is broken down", - }, - } - - def query_obj(self): - d = super(DistributionPieViz, self).query_obj() # noqa - fd = self.form_data - d['is_timeseries'] = False - gb = fd.get('groupby') or [] - cols = fd.get('columns') or [] - d['groupby'] = set(gb + cols) - if len(d['groupby']) < len(gb) + len(cols): - raise Exception("Can't have overlap between Series and Breakdowns") - if not self.metrics: - raise Exception("Pick at least one metric") - if not self.groupby: - raise Exception("Pick at least one field for [Series]") - return d - - def get_df(self, query_obj=None): - df = super(DistributionPieViz, self).get_df(query_obj) # noqa - fd = self.form_data - - row = df.groupby(self.groupby).sum()[self.metrics[0]].copy() - row.sort(ascending=False) - columns = fd.get('columns') or [] - pt = df.pivot_table( - index=self.groupby, - columns=columns, - values=self.metrics) - pt = pt.reindex(row.index) - return pt - - def get_json_data(self): - df = self.get_df() - series = df.to_dict('series') - chart_data = [] - for name, ys in series.items(): - if df[name].dtype.kind not in "biufc": - continue - if isinstance(name, string_types): - series_title = name - elif len(self.metrics) > 1: - series_title = ", ".join(name) - else: - l = [str(s) for s in name[1:]] - series_title = ", ".join(l) - d = { - "key": series_title, - "values": [ - {'x': i, 'y': v} - for i, v in ys.iteritems()] - } - chart_data.append(d) - return dumps(chart_data) - - -class SunburstViz(BaseViz): - viz_type = "sunburst" - verbose_name = "Sunburst" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'groupby', - 'metric', 'secondary_metric', - 'row_limit', - ) - },) - form_overrides = { - 'metric': { - 'label': 'Primary Metric', - 'description': ( - "The primary metric is used to " - "define the arc segment sizes"), - }, - 'secondary_metric': { - 'label': 'Secondary Metric', - 'description': ( - "This secondary metric is used to " - "define the color as a ratio against the primary metric. " - "If the two metrics match, color is mapped level groups"), - }, - 'groupby': { - 'label': 'Hierarchy', - 'description': "This defines the level of the hierarchy", - }, - } - - def get_df(self, query_obj=None): - df = super(SunburstViz, self).get_df(query_obj) - return df - - def get_json_data(self): - df = self.get_df() - - # if m1 == m2 duplicate the metric column - cols = self.form_data.get('groupby') - metric = self.form_data.get('metric') - secondary_metric = self.form_data.get('secondary_metric') - if metric == secondary_metric: - ndf = df[cols] - ndf['m1'] = df[metric] - ndf['m2'] = df[metric] - else: - cols += [ - self.form_data['metric'], self.form_data['secondary_metric']] - ndf = df[cols] - return ndf.to_json(orient="values") - - def query_obj(self): - qry = super(SunburstViz, self).query_obj() - qry['metrics'] = [ - self.form_data['metric'], self.form_data['secondary_metric']] - return qry - - -class SankeyViz(BaseViz): - viz_type = "sankey" - verbose_name = "Sankey" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'groupby', - 'metric', - 'row_limit', - ) - },) - form_overrides = { - 'groupby': { - 'label': 'Source / Target', - 'description': "Choose a source and a target", - }, - } - - def query_obj(self): - qry = super(SankeyViz, self).query_obj() - if len(qry['groupby']) != 2: - raise Exception("Pick exactly 2 columns as [Source / Target]") - qry['metrics'] = [ - self.form_data['metric']] - return qry - - def get_json_data(self): - df = self.get_df() - df.columns = ['source', 'target', 'value'] - d = df.to_dict(orient='records') - return dumps(d) - - -class DirectedForceViz(BaseViz): - viz_type = "directed_force" - verbose_name = "Directed Force Layout" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'groupby', - 'metric', - 'row_limit', - ) - }, - { - 'label': 'Force Layout', - 'fields': ( - 'link_length', - 'charge', - ) - },) - form_overrides = { - 'groupby': { - 'label': 'Source / Target', - 'description': "Choose a source and a target", - }, - } - def query_obj(self): - qry = super(DirectedForceViz, self).query_obj() - if len(self.form_data['groupby']) != 2: - raise Exception("Pick exactly 2 columns to 'Group By'") - qry['metrics'] = [self.form_data['metric']] - return qry - - def get_json_data(self): - df = self.get_df() - df.columns = ['source', 'target', 'value'] - d = df.to_dict(orient='records') - return dumps(d) - - -class WorldMapViz(BaseViz): - viz_type = "world_map" - verbose_name = "World Map" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'entity', - 'country_fieldtype', - 'metric', - ) - }, - { - 'label': 'Bubbles', - 'fields': ( - ('show_bubbles', None), - 'secondary_metric', - 'max_bubble_size', - ) - }) - form_overrides = { - 'entity': { - 'label': 'Country Field', - 'description': "3 letter code of the country", - }, - 'metric': { - 'label': 'Metric for color', - 'description': ("Metric that defines the color of the country"), - }, - 'secondary_metric': { - 'label': 'Bubble size', - 'description': ("Metric that defines the size of the bubble"), - }, - } - def query_obj(self): - qry = super(WorldMapViz, self).query_obj() - qry['metrics'] = [ - self.form_data['metric'], self.form_data['secondary_metric']] - qry['groupby'] = [self.form_data['entity']] - return qry - - def get_json_data(self): - from panoramix.data import countries - df = self.get_df() - cols = [self.form_data.get('entity')] - metric = self.form_data.get('metric') - secondary_metric = self.form_data.get('secondary_metric') - if metric == secondary_metric: - ndf = df[cols] - ndf['m1'] = df[metric] - ndf['m2'] = df[metric] - else: - cols += [metric, secondary_metric] - ndf = df[cols] - df = ndf - df.columns = ['country', 'm1', 'm2'] - d = df.to_dict(orient='records') - for row in d: - country = countries.get( - self.form_data.get('country_fieldtype'), row['country']) - if country: - row['country'] = country['cca3'] - row['latitude'] = country['lat'] - row['longitude'] = country['lng'] - row['name'] = country['name'] - else: - row['country'] = "XXX" - return dumps(d) - - -class FilterBoxViz(BaseViz): - viz_type = "filter_box" - verbose_name = "Filters" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'groupby', - 'metric', - ) - },) - form_overrides = { - 'groupby': { - 'label': 'Filter fields', - 'description': "The fields you want to filter on", - }, - } - def query_obj(self): - qry = super(FilterBoxViz, self).query_obj() - groupby = self.form_data['groupby'] - if len(groupby) < 1: - raise Exception("Pick at least one filter field") - qry['metrics'] = [ - self.form_data['metric']] - return qry - - def get_df(self, query_obj=None): - qry = self.query_obj() - - filters = [g for g in qry['groupby']] - d = {} - for flt in filters: - qry['groupby'] = [flt] - df = super(FilterBoxViz, self).get_df(qry) - d[flt] = [ - {'id': row[0], - 'text': row[0], - 'filter': flt, - 'metric': row[1]} - for row in df.itertuples(index=False)] - return d - - def get_json_data(self): - d = self.get_df() - return dumps(d) - - -class IFrameViz(BaseViz): - viz_type = "iframe" - verbose_name = "iFrame" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ('url',) - },) - - -class ParallelCoordinatesViz(BaseViz): - viz_type = "para" - verbose_name = "Parallel Coordinates" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'series', - 'metrics', - 'secondary_metric', - 'limit', - ('show_datatable', None), - ) - },) - def query_obj(self): - d = super(ParallelCoordinatesViz, self).query_obj() - fd = self.form_data - d['metrics'] = fd.get('metrics') - second = fd.get('secondary_metric') - if second not in d['metrics']: - d['metrics'] += [second] - d['groupby'] = [fd.get('series')] - return d - - def get_json_data(self): - df = self.get_df() - df = df[[self.form_data.get('series')] + self.form_data.get('metrics')] - return df.to_json(orient="records") - -class HeatmapViz(BaseViz): - viz_type = "heatmap" - verbose_name = "Heatmap" - is_timeseries = False - fieldsets = ( - { - 'label': None, - 'fields': ( - 'all_columns_x', - 'all_columns_y', - 'metric', - ) - }, - { - 'label': 'Heatmap Options', - 'fields': ( - 'linear_color_scheme', - ('xscale_interval', 'yscale_interval'), - 'canvas_image_rendering', - 'normalize_across', - ) - },) - def query_obj(self): - d = super(HeatmapViz, self).query_obj() - fd = self.form_data - d['metrics'] = [fd.get('metric')] - d['groupby'] = [fd.get('all_columns_x'), fd.get('all_columns_y')] - return d - - def get_json_data(self): - df = self.get_df() - fd = self.form_data - x = fd.get('all_columns_x') - y = fd.get('all_columns_y') - v = fd.get('metric') - if x == y: - df.columns = ['x', 'y', 'v'] - else: - df = df[[x, y, v]] - df.columns = ['x', 'y', 'v'] - norm = fd.get('normalize_across') - overall = False - if norm == 'heatmap': - overall = True - else: - gb = df.groupby(norm, group_keys=False) - if len(gb) <= 1: - overall = True - else: - df['perc'] = ( - gb.apply( - lambda x: (x.v - x.v.min()) / (x.v.max() - x.v.min())) - ) - if overall: - v = df.v - min_ = v.min() - df['perc'] = (v - min_) / (v.max() - min_) - return df.to_json(orient="records") - - -viz_types_list = [ - TableViz, - PivotTableViz, - NVD3TimeSeriesViz, - NVD3CompareTimeSeriesViz, - NVD3TimeSeriesStackedViz, - NVD3TimeSeriesBarViz, - DistributionBarViz, - DistributionPieViz, - BubbleViz, - MarkupViz, - WordCloudViz, - BigNumberViz, - SunburstViz, - DirectedForceViz, - SankeyViz, - WorldMapViz, - FilterBoxViz, - IFrameViz, - ParallelCoordinatesViz, - HeatmapViz, -] - -viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])