`, 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 some browsers, due to the limited stylability of ``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 6e5a8225613fb..0000000000000
--- 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 b53849c3673bf..0000000000000
--- 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 04095f106e4ab..0000000000000
--- 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 e4086e8bae5bf..0000000000000
--- 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 ef95359ffa2c7..0000000000000
--- 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 18fac5104a681..0000000000000
--- 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 872a5715a23f2..0000000000000
--- 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 f1b0461ec6315..0000000000000
--- 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 170eccba19454..0000000000000
--- 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 a0252067e6369..0000000000000
--- 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 15697836166da..0000000000000
--- 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 f4714b4637bbc..0000000000000
--- 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 bce1248212303..0000000000000
--- 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 1112ae3807cde..0000000000000
--- 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 0f9fddc8ba2fd..0000000000000
--- 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 f202c92dd92b3..0000000000000
--- 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 80c452b4d6f7f..0000000000000
--- 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 b9a15fa317d10..0000000000000
--- 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 += '' + p[fd.entity] + ' (' + p.group + ') ';
- s += row(fd.x, f(p.x));
- s += row(fd.y, f(p.y));
- s += row(fd.size, f(p.size));
- s += "
";
- 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 271989b9f1bcf..0000000000000
--- 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 8aef19a85922a..0000000000000
--- 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 795adbb297524..0000000000000
--- 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 9a2a0c88ae2e6..0000000000000
--- 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 ade452ae47530..0000000000000
--- 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 d2636faaa396e..0000000000000
--- 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 1e614ce946a1a..0000000000000
--- 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 cd70e14ebb51f..0000000000000
--- 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 937b0769bd191..0000000000000
--- 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 ab370b0bb1b21..0000000000000
--- 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 99e1bf6f0cc9f..0000000000000
--- 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 87f09b30be386..0000000000000
--- 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 465a04e1c8ac8..0000000000000
--- 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 e69de29bb2d1d..0000000000000
diff --git a/panoramix/bin/panoramix b/panoramix/bin/panoramix
deleted file mode 100755
index e6d7a37231b15..0000000000000
--- 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 4980a06cf8629..0000000000000
--- 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 8cd6798f34091..0000000000000
--- 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 9990ab9ccb65a..0000000000000
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 2652cf7242a91..0000000000000
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 6c71c0c43232c..0000000000000
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 253a68b9f50a1..0000000000000
--- 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 f81ef32df27fc..0000000000000
--- 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 233ed65a50375..0000000000000
--- 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 = ['' % widgets.html_params(name=field.name, **kwargs)]
- found = False
- for val, label, selected in field.iter_choices():
- html.append(self.render_option(val, label, selected))
- if field.data and val == field.data:
- found = True
- if not found:
- html.insert(1, self.render_option(field.data, field.data, True))
- html.append(' ')
- 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 98e4f9c44effe..0000000000000
--- 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 e69de29bb2d1d..0000000000000
diff --git a/panoramix/migrations/alembic.ini b/panoramix/migrations/alembic.ini
deleted file mode 100644
index f8ed4801f78bc..0000000000000
--- 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 e3713a3e49c36..0000000000000
--- 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 95702017ea341..0000000000000
--- 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 3158223743064..0000000000000
--- 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 0143aad58722b..0000000000000
--- 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 c6b88642b254b..0000000000000
--- 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 330b3b217c010..0000000000000
--- 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 4fac61ce9eb31..0000000000000
--- 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 6d64887b2ff32..0000000000000
--- 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 85b54bc5cc31c..0000000000000
--- 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 d9fdfaccea1b3..0000000000000
--- 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 aec2b32ed95c4..0000000000000
--- 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 c56ddc8f5fb26..0000000000000
--- 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 3b8a9bff452d1..0000000000000
--- 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 aade0b930aa0c..0000000000000
--- 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 66dc20aae35e2..0000000000000
--- 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 329af9ef2d78d..0000000000000
--- 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 54edc58a80a36..0000000000000
--- 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 3b20e44055969..0000000000000
--- 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 d64082e1bd919..0000000000000
--- 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 ec2e4be2f839d..0000000000000
--- 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 932170420302a..0000000000000
--- 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 50c8c9a458303..0000000000000
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 a65d5ed8b804f..0000000000000
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 e8dbcc49cc8e4..0000000000000
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 9478806cfaf85..0000000000000
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 83ecf8e57aa66..0000000000000
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 35fc3c161fd7d..0000000000000
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 42ebad239e9a3..0000000000000
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 01ae3939c49bf..0000000000000
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 8045764553404..0000000000000
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 14bfc440e414d..0000000000000
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 79a91666a5676..0000000000000
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 2d4604b6bdb56..0000000000000
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 68c53c4e8e770..0000000000000
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 33110ce164084..0000000000000
--- 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 aae0580c72ee7..0000000000000
--- 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 %}
-
diff --git a/panoramix/templates/appbuilder/navbar.html b/panoramix/templates/appbuilder/navbar.html
deleted file mode 100644
index d0d260c2c2384..0000000000000
--- 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 433bf1bf48db2..0000000000000
--- 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 %}
-
-
-
-
-
- {% if languages.keys()|length > 1 %}
-
- {% endif %}
-
-{% endmacro %}
-
-
-
-{% if not current_user.is_anonymous() %}
-
-
- {{g.user.get_full_name()}}
-
-
-
-{% else %}
-
- {{_("Login")}}
-{% endif %}
diff --git a/panoramix/templates/index.html b/panoramix/templates/index.html
deleted file mode 100644
index ab2339fe91e15..0000000000000
--- a/panoramix/templates/index.html
+++ /dev/null
@@ -1,116 +0,0 @@
-{% extends "appbuilder/base.html" %}
-
-{% block uncontained %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Panoramix
-
- an open source data visualization platform
-
-
-
-
-
-
-
-
Explore your data
-
-
- Intuitively navigate your data while slicing, dicing, and
- visualizing through a rich set of widgets
-
-
-
-
-
-
-
Create and share dashboards
-
Assemble many data visualization "slices" into a rich collection
-
-
-
-
-
-
Extend
-
Join the community and take part in extending the widget library
-
-
-
-
-
-
Connect
-
- Access data from MySql, Presto.db, Postgres, RedShift, Oracle, MsSql,
- SQLite, and more through the SqlAlchemy integration. You can also
- query realtime data blazingly fast out of Druid.io
-
-
-
-
-
-
-
-
-
-
-
-
-
Dashboards
-
Browse the dashboards list
-
-
-
-
-
-
-
Slices
-
"Slices" are individual views into a single dataset
-
-
-
-
-
-
-
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 b5d122680d719..0000000000000
--- 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 b075d52be60db..0000000000000
--- 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 8d0cba178ab4c..0000000000000
--- 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 27b20026677dc..0000000000000
--- a/panoramix/templates/panoramix/dashboard.html
+++ /dev/null
@@ -1,139 +0,0 @@
-{% extends "panoramix/basic.html" %}
-
-{% block head_js %}
- {{ super() }}
-
-{% endblock %}
-
-{% block body %}
-
-
-
-
-
-
-
-
-
- CSS template
- {% for t in templates %}
-
- {{ t.template_name }}
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ dashboard.dashboard_title }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% for slice in dashboard.slices %}
- {% set pos = pos_dict.get(slice.id, {}) %}
- {% set viz = slice.viz %}
-
-
-
-
-
-
-
- {{ slice.description_markeddown | safe }}
-
-
-
-
-
-
-
-
-
-
-
-
- {% endfor %}
-
-
-
-{% endblock %}
diff --git a/panoramix/templates/panoramix/explore.html b/panoramix/templates/panoramix/explore.html
deleted file mode 100644
index db6763faf5add..0000000000000
--- 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 36e37a9e7b699..0000000000000
--- a/panoramix/templates/panoramix/featured.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{% extends "panoramix/basic.html" %}
-
-{% block head_js %}
- {{ super() }}
-
-{% endblock %}
-
-{% block body %}
-
-
-
-
-
-
- Table
- Database
- Owner
-
-
-
-
- {% for dataset in featured_datasets %}
-
-
-
-
{{ dataset.table_name }}
-
{{ utils.markdown(dataset.description) | safe }}
-
-
- {{ dataset.database }}
- {{ dataset.owner or '' }}
-
-
- {% endfor %}
-
-
-
-
-{% endblock %}
-
diff --git a/panoramix/templates/panoramix/index.html b/panoramix/templates/panoramix/index.html
deleted file mode 100644
index 678f630949c03..0000000000000
--- 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 eede9ae0ac6da..0000000000000
--- 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 c32517bee7cb8..0000000000000
--- 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 398355ee2b016..0000000000000
--- 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 d2e30a6d803f6..0000000000000
--- 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 c4ba542a0f5fb..0000000000000
--- a/panoramix/templates/panoramix/sql.html
+++ /dev/null
@@ -1,99 +0,0 @@
-{% extends "panoramix/basic.html" %}
-
-{% block head_css %}
- {{super()}}
-
-
-
-{% endblock %}
-
-{% block body %}
-
-
-
db: [{{ db }}]
-
-
-
- Run!
- Create View
-
-
-
- {% for t in tables %}
-
- {{ t }}
-
- {% endfor %}
-
- SELECT *
-
-
-
-
-
-
-
-
-
-
-
-
-{% 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 5a0818d221de8..0000000000000
--- 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 %}
-
-
-
-
-
diff --git a/panoramix/templates/panoramix/traceback.html b/panoramix/templates/panoramix/traceback.html
deleted file mode 100644
index 97fc6b4961cfe..0000000000000
--- 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 ad801731eeb94..0000000000000
--- 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 f642be29c2e2f..0000000000000
--- 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 6e62d7fca5b2d..0000000000000
--- 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 83cd0c8a593be..0000000000000
--- 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 = '{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])