diff --git a/caravel/assets/javascripts/dashboard/Dashboard.jsx b/caravel/assets/javascripts/dashboard/Dashboard.jsx index 4fc8c009bc..99c2a1bd38 100644 --- a/caravel/assets/javascripts/dashboard/Dashboard.jsx +++ b/caravel/assets/javascripts/dashboard/Dashboard.jsx @@ -154,7 +154,7 @@ function dashboardContainer(dashboardData) { refreshExcept(sliceId) { const immune = this.metadata.filter_immune_slices || []; this.slices.forEach(function (slice) { - if (slice.data.sliceId !== sliceId && immune.indexOf(slice.data.sliceId) === -1) { + if (slice.data.slice_id !== sliceId && immune.indexOf(slice.data.sliceId) === -1) { slice.render(); } }); diff --git a/caravel/assets/javascripts/explore/explore.jsx b/caravel/assets/javascripts/explore/explore.jsx index 47d833b2a0..f2fcfd913b 100644 --- a/caravel/assets/javascripts/explore/explore.jsx +++ b/caravel/assets/javascripts/explore/explore.jsx @@ -475,9 +475,6 @@ $(document).ready(function () { initExploreView(); - // Dynamically register this visualization - px.registerViz(data.viz_name || 'table'); - slice = px.Slice(data); $('.slice').data('slice', slice); diff --git a/caravel/assets/javascripts/modules/caravel.js b/caravel/assets/javascripts/modules/caravel.js index f19b5a66fe..1a851e3a43 100644 --- a/caravel/assets/javascripts/modules/caravel.js +++ b/caravel/assets/javascripts/modules/caravel.js @@ -1,210 +1,19 @@ import $ from 'jquery'; -const d3 = require('d3'); const Mustache = require('mustache'); const utils = require('./utils'); // vis sources /* eslint camel-case: 0 */ -const sourceMap = { - area: 'nvd3_vis.js', - bar: 'nvd3_vis.js', - bubble: 'nvd3_vis.js', - big_number: 'big_number.js', - big_number_total: 'big_number.js', - compare: 'nvd3_vis.js', - dist_bar: 'nvd3_vis.js', - directed_force: 'directed_force.js', - filter_box: 'filter_box.js', - heatmap: 'heatmap.js', - iframe: 'iframe.js', - line: 'nvd3_vis.js', - markup: 'markup.js', - separator: 'markup.js', - para: 'parallel_coordinates.js', - pie: 'nvd3_vis.js', - box_plot: 'nvd3_vis.js', - pivot_table: 'pivot_table.js', - sankey: 'sankey.js', - sunburst: 'sunburst.js', - table: 'table.js', - word_cloud: 'word_cloud.js', - world_map: 'world_map.js', - treemap: 'treemap.js', - cal_heatmap: 'cal_heatmap.js', - horizon: 'horizon.js', - mapbox: 'mapbox.jsx', - histogram: 'histogram.js', -}; -const color = function () { - // Color related utility functions go in this object - const bnbColors = [ - '#ff5a5f', // rausch - '#7b0051', // hackb - '#007A87', // kazan - '#00d1c1', // babu - '#8ce071', // lima - '#ffb400', // beach - '#b4a76c', // barol - '#ff8083', - '#cc0086', - '#00a1b3', - '#00ffeb', - '#bbedab', - '#ffd266', - '#cbc29a', - '#ff3339', - '#ff1ab1', - '#005c66', - '#00b3a5', - '#55d12e', - '#b37e00', - '#988b4e', - ]; - const spectrums = { - blue_white_yellow: [ - '#00d1c1', - 'white', - '#ffb400', - ], - fire: [ - 'white', - 'yellow', - 'red', - 'black', - ], - white_black: [ - 'white', - 'black', - ], - black_white: [ - 'black', - 'white', - ], - }; - const colorBnb = function () { - // Color factory - const seen = {}; - return function (s) { - if (!s) { - return; - } - let stringifyS = String(s); - // next line is for caravel series that should have the same color - stringifyS = stringifyS.replace('---', ''); - if (seen[stringifyS] === undefined) { - seen[stringifyS] = Object.keys(seen).length; - } - /* eslint consistent-return: 0 */ - return this.bnbColors[seen[stringifyS] % this.bnbColors.length]; - }; - }; - const colorScalerFactory = function (colors, data, accessor) { - // Returns a linear scaler our of an array of color - if (!Array.isArray(colors)) { - /* eslint no-param-reassign: 0 */ - colors = spectrums[colors]; - } - let ext = [ - 0, - 1, - ]; - if (data !== undefined) { - ext = d3.extent(data, accessor); - } - const points = []; - const chunkSize = (ext[1] - ext[0]) / colors.length; - $.each(colors, function (i) { - points.push(i * chunkSize); - }); - return d3.scale.linear().domain(points).range(colors); - }; - return { - bnbColors, - category21: colorBnb(), - colorScalerFactory, - }; -}; +import vizMap from '../../visualizations/main.js'; + /* eslint wrap-iife: 0*/ const px = function () { - const visualizations = {}; let slice; function getParam(name) { - name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); - const regex = new RegExp('[\\?&]' + name + '=([^]*)'); + const formattedName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); + const regex = new RegExp('[\\?&]' + formattedName + '=([^]*)'); const results = regex.exec(location.search); return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); } - function UTC(dttm) { - return new Date( - dttm.getUTCFullYear(), - dttm.getUTCMonth(), - dttm.getUTCDate(), - dttm.getUTCHours(), - dttm.getUTCMinutes(), - dttm.getUTCSeconds() - ); - } - const tickMultiFormat = d3.time.format.multi([ - [ - '.%L', - function (d) { - return d.getMilliseconds(); - }, - ], - // If there are millisections, show only them - [ - ':%S', - function (d) { - return d.getSeconds(); - }, - ], - // If there are seconds, show only them - [ - '%a %b %d, %I:%M %p', - function (d) { - return d.getMinutes() !== 0; - }, - ], - // If there are non-zero minutes, show Date, Hour:Minute [AM/PM] - [ - '%a %b %d, %I %p', - function (d) { - return d.getHours() !== 0; - }, - ], - // If there are hours that are multiples of 3, show date and AM/PM - [ - '%a %b %d', - function (d) { - return d.getDate() !== 1; - }, - ], - // If not the first of the month, do "month day, year." - [ - '%B %Y', - function (d) { - return d.getMonth() !== 0 && d.getDate() === 1; - }, - ], - // If the first of the month, do "month day, year." - [ - '%Y', - function () { - return true; - }, - ], // fall back on month, year - ]); - function formatDate(dttm) { - const d = UTC(new Date(dttm)); - // d = new Date(d.getTime() - 1 * 60 * 60 * 1000); - return tickMultiFormat(d); - } - function timeFormatFactory(d3timeFormat) { - const f = d3.time.format(d3timeFormat); - return function (dttm) { - const d = UTC(new Date(dttm)); - return f(d); - }; - } function initFavStars() { const baseUrl = '/caravel/favstar/'; // Init star behavihor for favorite @@ -267,12 +76,12 @@ const px = function () { containerId, selector, querystring(params) { - params = params || {}; + const newParams = params || {}; const parser = document.createElement('a'); parser.href = data.json_endpoint; if (dashboard !== undefined) { const flts = - params.extraFilters === false ? '' : + newParams.extraFilters === false ? '' : encodeURIComponent(JSON.stringify(dashboard.filters)); qrystr = parser.search + '&extra_filters=' + flts; } else if ($('#query').length === 0) { @@ -293,10 +102,10 @@ const px = function () { return Mustache.render(s, context); }, jsonEndpoint(params) { - params = params || {}; + const newParams = params || {}; const parser = document.createElement('a'); parser.href = data.json_endpoint; - let endpoint = parser.pathname + this.querystring({ extraFilters: params.extraFilters }); + let endpoint = parser.pathname + this.querystring({ extraFilters: newParams.extraFilters }); endpoint += '&json=true'; endpoint += '&force=' + this.force; return endpoint; @@ -413,9 +222,10 @@ const px = function () { }, render(force) { if (force === undefined) { - force = false; + this.force = false; + } else { + this.force = force; } - this.force = force; token.find('img.loading').show(); container.css('height', this.height()); dttm = 0; @@ -457,32 +267,14 @@ const px = function () { } }, }; - const visType = data.form_data.viz_type; - px.registerViz(visType); - slice.viz = visualizations[data.form_data.viz_type](slice); + slice.viz = vizMap[data.form_data.viz_type](slice); return slice; }; - function registerViz(name) { - const visSource = sourceMap[name]; - if (visSource) { - /* eslint global-require: 0 */ - const visFactory = require('../../visualizations/' + visSource); - if (typeof visFactory === 'function') { - visualizations[name] = visFactory; - } - } else { - throw new Error('require(' + name + ') failed.'); - } - } // Export public functions return { - color: color(), - formatDate, getParam, initFavStars, - registerViz, Slice, - timeFormatFactory, }; }(); module.exports = px; diff --git a/caravel/assets/javascripts/modules/colors.js b/caravel/assets/javascripts/modules/colors.js new file mode 100644 index 0000000000..adc92d1092 --- /dev/null +++ b/caravel/assets/javascripts/modules/colors.js @@ -0,0 +1,85 @@ +import $ from 'jquery'; +const d3 = require('d3'); + +// Color related utility functions go in this object +export const bnbColors = [ + '#ff5a5f', // rausch + '#7b0051', // hackb + '#007A87', // kazan + '#00d1c1', // babu + '#8ce071', // lima + '#ffb400', // beach + '#b4a76c', // barol + '#ff8083', + '#cc0086', + '#00a1b3', + '#00ffeb', + '#bbedab', + '#ffd266', + '#cbc29a', + '#ff3339', + '#ff1ab1', + '#005c66', + '#00b3a5', + '#55d12e', + '#b37e00', + '#988b4e', +]; + +const spectrums = { + blue_white_yellow: [ + '#00d1c1', + 'white', + '#ffb400', + ], + fire: [ + 'white', + 'yellow', + 'red', + 'black', + ], + white_black: [ + 'white', + 'black', + ], + black_white: [ + 'black', + 'white', + ], +}; + +export const category21 = (function () { + // Color factory + const seen = {}; + return function (s) { + if (!s) { + return; + } + let stringifyS = String(s); + // next line is for caravel series that should have the same color + stringifyS = stringifyS.replace('---', ''); + if (seen[stringifyS] === undefined) { + seen[stringifyS] = Object.keys(seen).length; + } + /* eslint consistent-return: 0 */ + return bnbColors[seen[stringifyS] % bnbColors.length]; + }; +}()); + +export const colorScalerFactory = function (colors, data, accessor) { + // Returns a linear scaler our of an array of color + if (!Array.isArray(colors)) { + /* eslint no-param-reassign: 0 */ + colors = spectrums[colors]; + } + let ext = [0, 1]; + if (data !== undefined) { + ext = d3.extent(data, accessor); + } + const points = []; + const chunkSize = (ext[1] - ext[0]) / colors.length; + $.each(colors, function (i) { + points.push(i * chunkSize); + }); + return d3.scale.linear().domain(points).range(colors); +}; diff --git a/caravel/assets/javascripts/modules/dates.js b/caravel/assets/javascripts/modules/dates.js new file mode 100644 index 0000000000..a746ce102f --- /dev/null +++ b/caravel/assets/javascripts/modules/dates.js @@ -0,0 +1,74 @@ +const d3 = require('d3'); + +function UTC(dttm) { + return new Date( + dttm.getUTCFullYear(), + dttm.getUTCMonth(), + dttm.getUTCDate(), + dttm.getUTCHours(), + dttm.getUTCMinutes(), + dttm.getUTCSeconds() + ); +} +export const tickMultiFormat = d3.time.format.multi([ + [ + '.%L', + function (d) { + return d.getMilliseconds(); + }, + ], + // If there are millisections, show only them + [ + ':%S', + function (d) { + return d.getSeconds(); + }, + ], + // If there are seconds, show only them + [ + '%a %b %d, %I:%M %p', + function (d) { + return d.getMinutes() !== 0; + }, + ], + // If there are non-zero minutes, show Date, Hour:Minute [AM/PM] + [ + '%a %b %d, %I %p', + function (d) { + return d.getHours() !== 0; + }, + ], + // If there are hours that are multiples of 3, show date and AM/PM + [ + '%a %b %d', + function (d) { + return d.getDate() !== 1; + }, + ], + // If not the first of the month, do "month day, year." + [ + '%B %Y', + function (d) { + return d.getMonth() !== 0 && d.getDate() === 1; + }, + ], + // If the first of the month, do "month day, year." + [ + '%Y', + function () { + return true; + }, + ], // fall back on month, year +]); +export const formatDate = function (dttm) { + const d = UTC(new Date(dttm)); + // d = new Date(d.getTime() - 1 * 60 * 60 * 1000); + return tickMultiFormat(d); +}; +export const timeFormatFactory = function (d3timeFormat) { + const f = d3.time.format(d3timeFormat); + return function (dttm) { + const d = UTC(new Date(dttm)); + return f(d); + }; +}; diff --git a/caravel/assets/javascripts/modules/utils.js b/caravel/assets/javascripts/modules/utils.js index 9417998adf..3d37ef608b 100644 --- a/caravel/assets/javascripts/modules/utils.js +++ b/caravel/assets/javascripts/modules/utils.js @@ -1,5 +1,5 @@ -const $ = require('jquery'); const d3 = require('d3'); +const $ = require('jquery'); /* Utility function that takes a d3 svg:text selection and a max width, and splits the text's text across multiple tspan lines such that any given line does not exceed max width @@ -7,7 +7,7 @@ const d3 = require('d3'); If text does not span multiple lines AND adjustedY is passed, will set the text to the passed val */ -function wrapSvgText(text, width, adjustedY) { +export function wrapSvgText(text, width, adjustedY) { const lineHeight = 1; // ems text.each(function () { diff --git a/caravel/assets/package.json b/caravel/assets/package.json index 4bf1a95644..c32e7facde 100644 --- a/caravel/assets/package.json +++ b/caravel/assets/package.json @@ -59,6 +59,7 @@ "nvd3": "1.8.4", "react": "^15.2.1", "react-bootstrap": "^0.28.3", + "react-bootstrap-datetimepicker": "0.0.22", "react-bootstrap-table": "^2.3.7", "react-dom": "^0.14.8", "react-grid-layout": "^0.12.3", diff --git a/caravel/assets/stylesheets/less/bootswatch.less b/caravel/assets/stylesheets/less/bootswatch.less index 2223157f26..6acb542c61 100644 --- a/caravel/assets/stylesheets/less/bootswatch.less +++ b/caravel/assets/stylesheets/less/bootswatch.less @@ -107,7 +107,7 @@ .btn + .btn-group, .btn-group + .btn, .btn-group + .btn-group { - margin-left: -1; + margin-left: -2; } &-vertical { diff --git a/caravel/assets/stylesheets/react-select/select.less b/caravel/assets/stylesheets/react-select/select.less new file mode 100644 index 0000000000..833adb62dd --- /dev/null +++ b/caravel/assets/stylesheets/react-select/select.less @@ -0,0 +1,93 @@ +// @mistercrunch +@select-primary-color: black; + +/** + * React Select + * ============ + * Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/ + * https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs + * MIT License: https://github.com/keystonejs/react-select +*/ + +// Variables +// ------------------------------ + +// common +//@select-primary-color: #007eff; + +// control options +@select-input-bg: #fff; +@select-input-bg-disabled: #f9f9f9; +@select-input-border-color: #ccc; +@select-input-border-radius: 4px; +@select-input-border-focus: @select-primary-color; +@select-input-border-width: 1px; +@select-input-height: 36px; +@select-input-internal-height: (@select-input-height - (@select-input-border-width * 2)); +@select-input-placeholder: #aaa; +@select-text-color: #333; +@select-link-hover-color: @select-input-border-focus; + +@select-padding-vertical: 8px; +@select-padding-horizontal: 10px; + +// menu options +@select-menu-zindex: 1; +@select-menu-max-height: 200px; + +@select-option-color: lighten(@select-text-color, 20%); +@select-option-bg: @select-input-bg; +@select-option-focused-color: @select-text-color; +@select-option-focused-bg: fade(@select-primary-color, 8%); +@select-option-focused-bg-fb: mix(@select-primary-color, @select-option-bg, 8%); // Fallback color for IE 8 +@select-option-selected-color: @select-text-color; +@select-option-selected-bg: fade(@select-primary-color, 4%); +@select-option-selected-bg-fb: mix(@select-primary-color, @select-option-bg, 4%); // Fallback color for IE 8 +@select-option-disabled-color: lighten(@select-text-color, 60%); + +@select-noresults-color: lighten(@select-text-color, 40%); + +// clear "x" button +@select-clear-size: floor((@select-input-height / 2)); +@select-clear-color: #999; +@select-clear-hover-color: #D0021B; // red +@select-clear-width: (@select-input-internal-height / 2); + +// arrow indicator +@select-arrow-color: #999; +@select-arrow-color-hover: #666; +@select-arrow-width: 5px; + +// loading indicator +@select-loading-size: 16px; +@select-loading-color: @select-text-color; +@select-loading-color-bg: @select-input-border-color; + +// multi-select item +@select-item-font-size: .9em; + +@select-item-bg: fade(@select-primary-color, 8%); +@select-item-bg-fb: mix(@select-primary-color, @select-input-bg, 8%); // Fallback color for IE 8 +@select-item-color: @select-primary-color; +@select-item-border-color: fade(@select-primary-color, 24%); +@select-item-border-color-fb: mix(@select-primary-color, @select-input-bg, 24%); // Fallback color for IE 8 +@select-item-hover-color: darken(@select-item-color, 5%); +@select-item-hover-bg: darken(@select-item-bg, 5%); +@select-item-hover-bg-fb: mix(darken(@select-primary-color, 5%), @select-item-bg-fb, 8%); // Fallback color for IE 8 +@select-item-disabled-color: #333; +@select-item-disabled-bg: #fcfcfc; +@select-item-disabled-border-color: darken(@select-item-disabled-bg, 10%); + +@select-item-border-radius: 2px; +@select-item-gutter: 5px; + +@select-item-padding-horizontal: 5px; +@select-item-padding-vertical: 2px; + +// imports +// @mistercrunch: these were altered to point to react-select/less +@import "../../node_modules/react-select/less/control.less"; +@import "../../node_modules/react-select/less/menu.less"; +@import "../../node_modules/react-select/less/mixins.less"; +@import "../../node_modules/react-select/less/multi.less"; +@import "../../node_modules/react-select/less/spinner.less"; diff --git a/caravel/assets/visualizations/big_number.js b/caravel/assets/visualizations/big_number.js index d97728f01b..b4777ca99f 100644 --- a/caravel/assets/visualizations/big_number.js +++ b/caravel/assets/visualizations/big_number.js @@ -4,7 +4,7 @@ var d3 = window.d3 || require('d3'); // CSS require('./big_number.css'); -var px = require('../javascripts/modules/caravel.js'); +import { formatDate } from '../javascripts/modules/dates' function bigNumberVis(slice) { var div = d3.select(slice.selector); @@ -140,7 +140,7 @@ function bigNumberVis(slice) { .scale(scale_x) .orient('bottom') .ticks(4) - .tickFormat(px.formatDate); + .tickFormat(formatDate); g.call(x_axis); g.attr('transform', 'translate(0,' + (height - margin) + ')'); diff --git a/caravel/assets/visualizations/filter_box.css b/caravel/assets/visualizations/filter_box.css index 4316f25e56..43b193032e 100644 --- a/caravel/assets/visualizations/filter_box.css +++ b/caravel/assets/visualizations/filter_box.css @@ -4,19 +4,26 @@ } .dashboard .filter_box .slice_container > div { - padding-top: 0; + padding-top: 0; } ul.select2-results li.select2-highlighted div.filter_box{ - color: black; - border-width: 1px; - border-style: solid; - border-color: #666; + color: black; + border-width: 1px; + border-style: solid; + border-color: #666; } ul.select2-results div.filter_box{ - color: black; - border-style: solid; - border-width: 1px; - border-color: transparent; + color: black; + border-style: solid; + border-width: 1px; + border-color: transparent; +} +.filter_box .slice_container { + padding: 10px; + overflow: visible !important; +} +.filter_box:hover { + z-index: 1000; } diff --git a/caravel/assets/visualizations/filter_box.js b/caravel/assets/visualizations/filter_box.js deleted file mode 100644 index c7844708b6..0000000000 --- a/caravel/assets/visualizations/filter_box.js +++ /dev/null @@ -1,92 +0,0 @@ -// JS -var $ = window.$ = require('jquery'); -var jQuery = window.jQuery = $; -var d3 = window.d3 || require('d3'); - -// CSS -require('./filter_box.css'); -require('../javascripts/caravel-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); - - var preSelectDict = slice.getFilters() || {}; - $.getJSON(slice.jsonEndpoint({ - // filter box should ignore the filters - // otherwise there will be only a few options in the dropdown menu - extraFilters: false, - }), 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); - - var preSelect = preSelectDict[filter]; - if (preSelect !== undefined) { - filtersObj[filter].select2('val', preSelect); - } - } - slice.done(payload); - - 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 '