From 23a8adb5954cc36eae879870f193eaafdc1695bd Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Wed, 4 Oct 2017 10:17:33 -0700 Subject: [PATCH] New "Time Series - Table" visualization (#3543) * [WiP] adding a new "Time Series - Table" viz * Adding drag-n-drop to collection * Using keys in arrays * tests --- .../images/viz_thumbnails/time_table.png | Bin 0 -> 65153 bytes .../components/InfoTooltipWithTrigger.jsx | 20 +- .../javascripts/components/MetricOption.jsx | 21 +- .../explore/components/Control.jsx | 27 +-- .../components/controls/BoundsControl.jsx | 5 - .../components/controls/CollectionControl.jsx | 119 ++++++++++ .../controls/TimeSeriesColumnControl.jsx | 223 ++++++++++++++++++ .../explore/components/controls/index.jsx | 33 +++ superset/assets/javascripts/explore/main.css | 5 +- .../javascripts/explore/stores/controls.jsx | 8 + .../javascripts/explore/stores/visTypes.js | 19 ++ superset/assets/javascripts/modules/colors.js | 2 + superset/assets/js_build.sh | 1 - superset/assets/package.json | 6 +- .../TimeSeriesColumnControl_spec.jsx | 33 +++ superset/assets/stylesheets/superset.less | 7 + superset/assets/visualizations/main.js | 1 + superset/assets/visualizations/time_table.css | 3 + superset/assets/visualizations/time_table.jsx | 173 ++++++++++++++ superset/viz.py | 94 ++++---- 20 files changed, 710 insertions(+), 90 deletions(-) create mode 100644 superset/assets/images/viz_thumbnails/time_table.png create mode 100644 superset/assets/javascripts/explore/components/controls/CollectionControl.jsx create mode 100644 superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx create mode 100644 superset/assets/javascripts/explore/components/controls/index.jsx create mode 100644 superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx create mode 100644 superset/assets/visualizations/time_table.css create mode 100644 superset/assets/visualizations/time_table.jsx diff --git a/superset/assets/images/viz_thumbnails/time_table.png b/superset/assets/images/viz_thumbnails/time_table.png new file mode 100644 index 0000000000000000000000000000000000000000..5eba0c2f0b33d184ca51e6d6c2ccf57cc0b4efcd GIT binary patch literal 65153 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 literal 0 HcmV?d00001 diff --git a/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx b/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx index 09632cdf538cf..d86d0515e718c 100644 --- a/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx +++ b/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx @@ -5,7 +5,7 @@ import { slugify } from '../modules/utils'; const propTypes = { label: PropTypes.string.isRequired, - tooltip: PropTypes.string.isRequired, + tooltip: PropTypes.string, icon: PropTypes.string, className: PropTypes.string, onClick: PropTypes.func, @@ -17,11 +17,21 @@ const defaultProps = { className: 'text-muted', placement: 'right', }; +const tooltipStyle = { wordWrap: 'break-word' }; export default function InfoTooltipWithTrigger({ label, tooltip, icon, className, onClick, placement, bsStyle }) { const iconClass = `fa fa-${icon} ${className} ${bsStyle ? 'text-' + bsStyle : ''}`; - const tooltipStyle = { wordWrap: 'break-word' }; + const iconEl = ( + + ); + if (!tooltip) { + return iconEl; + } return ( } > - + {iconEl} ); } diff --git a/superset/assets/javascripts/components/MetricOption.jsx b/superset/assets/javascripts/components/MetricOption.jsx index b19043479d647..f0994142569b8 100644 --- a/superset/assets/javascripts/components/MetricOption.jsx +++ b/superset/assets/javascripts/components/MetricOption.jsx @@ -5,9 +5,13 @@ import InfoTooltipWithTrigger from './InfoTooltipWithTrigger'; const propTypes = { metric: PropTypes.object.isRequired, + showFormula: PropTypes.bool, +}; +const defaultProps = { + showFormula: true, }; -export default function MetricOption({ metric }) { +export default function MetricOption({ metric, showFormula }) { return (
@@ -21,12 +25,14 @@ export default function MetricOption({ metric }) { label={`descr-${metric.metric_name}`} /> } - + {showFormula && + + } {metric.warning_text && ); } MetricOption.propTypes = propTypes; +MetricOption.defaultProps = defaultProps; diff --git a/superset/assets/javascripts/explore/components/Control.jsx b/superset/assets/javascripts/explore/components/Control.jsx index 972ff0d3c19d4..ed7ea97ad31d6 100644 --- a/superset/assets/javascripts/explore/components/Control.jsx +++ b/superset/assets/javascripts/explore/components/Control.jsx @@ -1,33 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import BoundsControl from './controls/BoundsControl'; -import CheckboxControl from './controls/CheckboxControl'; -import ColorSchemeControl from './controls/ColorSchemeControl'; -import DatasourceControl from './controls/DatasourceControl'; -import DateFilterControl from './controls/DateFilterControl'; -import FilterControl from './controls/FilterControl'; -import HiddenControl from './controls/HiddenControl'; -import SelectAsyncControl from './controls/SelectAsyncControl'; -import SelectControl from './controls/SelectControl'; -import TextAreaControl from './controls/TextAreaControl'; -import TextControl from './controls/TextControl'; -import VizTypeControl from './controls/VizTypeControl'; +import controlMap from './controls'; -const controlMap = { - BoundsControl, - CheckboxControl, - DatasourceControl, - DateFilterControl, - FilterControl, - HiddenControl, - SelectControl, - TextAreaControl, - TextControl, - VizTypeControl, - ColorSchemeControl, - SelectAsyncControl, -}; const controlTypes = Object.keys(controlMap); const propTypes = { diff --git a/superset/assets/javascripts/explore/components/controls/BoundsControl.jsx b/superset/assets/javascripts/explore/components/controls/BoundsControl.jsx index 776f7a499bde0..803a539619560 100644 --- a/superset/assets/javascripts/explore/components/controls/BoundsControl.jsx +++ b/superset/assets/javascripts/explore/components/controls/BoundsControl.jsx @@ -5,16 +5,11 @@ import ControlHeader from '../ControlHeader'; import { t } from '../../../locales'; const propTypes = { - name: PropTypes.string.isRequired, - label: PropTypes.string, - description: PropTypes.string, onChange: PropTypes.func, value: PropTypes.array, }; const defaultProps = { - label: null, - description: null, onChange: () => {}, value: [null, null], }; diff --git a/superset/assets/javascripts/explore/components/controls/CollectionControl.jsx b/superset/assets/javascripts/explore/components/controls/CollectionControl.jsx new file mode 100644 index 0000000000000..74f0d9b45cd6f --- /dev/null +++ b/superset/assets/javascripts/explore/components/controls/CollectionControl.jsx @@ -0,0 +1,119 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ListGroup, ListGroupItem } from 'react-bootstrap'; +import shortid from 'shortid'; +import { + SortableContainer, SortableHandle, SortableElement, arrayMove, +} from 'react-sortable-hoc'; + +import InfoTooltipWithTrigger from '../../../components/InfoTooltipWithTrigger'; +import ControlHeader from '../ControlHeader'; + +const propTypes = { + name: PropTypes.string.isRequired, + label: PropTypes.string, + description: PropTypes.string, + placeholder: PropTypes.string, + addTooltip: PropTypes.string, + itemGenerator: PropTypes.func, + keyAccessor: PropTypes.func, + onChange: PropTypes.func, + value: PropTypes.oneOfType([ + PropTypes.array, + ]), + isFloat: PropTypes.bool, + isInt: PropTypes.bool, + control: PropTypes.func, +}; + +const defaultProps = { + label: null, + description: null, + onChange: () => {}, + placeholder: 'Empty collection', + itemGenerator: () => ({ key: shortid.generate() }), + keyAccessor: o => o.key, + value: [], + addTooltip: 'Add an item', +}; +const SortableListGroupItem = SortableElement(ListGroupItem); +const SortableListGroup = SortableContainer(ListGroup); +const SortableDragger = SortableHandle(() => ( + )); + +export default class CollectionControl extends React.Component { + constructor(props) { + super(props); + this.onAdd = this.onAdd.bind(this); + } + onChange(i, value) { + Object.assign(this.props.value[i], value); + this.props.onChange(this.props.value); + } + onAdd() { + this.props.onChange(this.props.value.concat([this.props.itemGenerator()])); + } + onSortEnd({ oldIndex, newIndex }) { + this.props.onChange(arrayMove(this.props.value, oldIndex, newIndex)); + } + removeItem(i) { + this.props.onChange(this.props.value.filter((o, ix) => i !== ix)); + } + renderList() { + if (this.props.value.length === 0) { + return
{this.props.placeholder}
; + } + return ( + + {this.props.value.map((o, i) => ( + +
+ +
+
+ +
+
+ +
+
))} +
+ ); + } + render() { + return ( +
+ + {this.renderList()} + +
+ ); + } +} + +CollectionControl.propTypes = propTypes; +CollectionControl.defaultProps = defaultProps; diff --git a/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx b/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx new file mode 100644 index 0000000000000..cd8ec98c22912 --- /dev/null +++ b/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx @@ -0,0 +1,223 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Row, Col, FormControl, OverlayTrigger, Popover, +} from 'react-bootstrap'; +import Select from 'react-select'; + +import InfoTooltipWithTrigger from '../../../components/InfoTooltipWithTrigger'; +import BoundsControl from './BoundsControl'; + +const propTypes = { + onChange: PropTypes.func, +}; + +const defaultProps = { + onChange: () => {}, +}; + +const comparisonTypeOptions = [ + { value: 'value', label: 'Actual value' }, + { value: 'diff', label: 'Difference' }, + { value: 'perc', label: 'Percentage' }, + { value: 'perc_change', label: 'Percentage Change' }, +]; + +const colTypeOptions = [ + { value: 'time', label: 'Time Comparison' }, + { value: 'contrib', label: 'Contribution' }, + { value: 'spark', label: 'Sparkline' }, + { value: 'avg', label: 'Period Average' }, +]; + +export default class TimeSeriesColumnControl extends React.Component { + constructor(props) { + super(props); + const state = Object.assign({}, props); + delete state.onChange; + this.state = state; + this.onChange = this.onChange.bind(this); + } + onChange() { + this.props.onChange(this.state); + } + onSelectChange(attr, opt) { + this.setState({ [attr]: opt.value }, this.onChange); + } + onTextInputChange(attr, event) { + this.setState({ [attr]: event.target.value }, this.onChange); + } + onBoundsChange(bounds) { + this.setState({ bounds }, this.onChange); + } + setType() { + } + textSummary() { + return `${this.state.label}`; + } + edit() { + } + formRow(label, tooltip, ttLabel, control) { + return ( + + + {label}{' '} + + + {control} + + ); + } + renderPopover() { + return ( + +
+ {this.formRow( + 'Label', + 'The column header label', + 'time-lag', + , + )} + {this.formRow( + 'Tooltip', + 'Column header tooltip', + 'col-tooltip', + , + )} + {this.formRow( + 'Type', + 'Type of comparison, value difference or percentage', + 'col-type', + , + )} + {this.state.colType !== 'spark' && this.formRow( + 'Bounds', + ( + 'Number bounds used for color coding from red to green. ' + + 'Reverse the number for green to red. To get boolean ' + + 'red or green without spectrum, you can use either only ' + + 'min, or max, depending on whether small or big should be ' + + 'green or red.' + ), + 'bounds', + , + )} + {this.formRow( + 'D3 format', + 'D3 format string', + 'd3-format', + , + )} +
+
+ ); + } + render() { + return ( + + {this.textSummary()}{' '} + + + + + ); + } +} + +TimeSeriesColumnControl.propTypes = propTypes; +TimeSeriesColumnControl.defaultProps = defaultProps; diff --git a/superset/assets/javascripts/explore/components/controls/index.jsx b/superset/assets/javascripts/explore/components/controls/index.jsx new file mode 100644 index 0000000000000..499e6055f6b13 --- /dev/null +++ b/superset/assets/javascripts/explore/components/controls/index.jsx @@ -0,0 +1,33 @@ +import BoundsControl from './BoundsControl'; +import CheckboxControl from './CheckboxControl'; +import CollectionControl from './CollectionControl'; +import ColorSchemeControl from './ColorSchemeControl'; +import DatasourceControl from './DatasourceControl'; +import DateFilterControl from './DateFilterControl'; +import FilterControl from './FilterControl'; +import HiddenControl from './HiddenControl'; +import SelectAsyncControl from './SelectAsyncControl'; +import SelectControl from './SelectControl'; +import TextAreaControl from './TextAreaControl'; +import TextControl from './TextControl'; +import TimeSeriesColumnControl from './TimeSeriesColumnControl'; +import VizTypeControl from './VizTypeControl'; + +const controlMap = { + BoundsControl, + CheckboxControl, + CollectionControl, + ColorSchemeControl, + DatasourceControl, + DateFilterControl, + FilterControl, + HiddenControl, + SelectAsyncControl, + SelectControl, + TextAreaControl, + TextControl, + TimeSeriesColumnControl, + VizTypeControl, +}; + +export default controlMap; diff --git a/superset/assets/javascripts/explore/main.css b/superset/assets/javascripts/explore/main.css index 684fdf0e8b011..bc67249e75bd7 100644 --- a/superset/assets/javascripts/explore/main.css +++ b/superset/assets/javascripts/explore/main.css @@ -109,4 +109,7 @@ } .save-modal-selector { margin: 10px 0; -} \ No newline at end of file +} +.list-group { + margin-bottom: 10px; +} diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index 92faad17393da..57222a6efcfd6 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -5,6 +5,7 @@ import { ALL_COLOR_SCHEMES, spectrums } from '../../modules/colors'; import MetricOption from '../../components/MetricOption'; import ColumnOption from '../../components/ColumnOption'; import { t } from '../../locales'; +import controlMap from '../components/controls'; const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format'; @@ -1410,5 +1411,12 @@ export const controls = { default: 4, description: 'Number of decimal places with which to display lift values', }, + column_collection: { + type: 'CollectionControl', + label: t('Time Series Columns'), + validators: [v.nonEmpty], + control: controlMap.TimeSeriesColumnControl, + }, + }; export default controls; diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js index 5648f4bbfc0b6..1d4d79b5b47dd 100644 --- a/superset/assets/javascripts/explore/stores/visTypes.js +++ b/superset/assets/javascripts/explore/stores/visTypes.js @@ -369,6 +369,25 @@ export const visTypes = { }, }, + time_table: { + label: t('Time Series Table'), + controlPanelSections: [ + { + label: t('Query'), + expanded: true, + controlSetRows: [ + ['groupby', 'metrics'], + ['column_collection'], + ], + }, + ], + controlOverrides: { + groupby: { + multiple: false, + }, + }, + }, + markup: { label: t('Markup'), controlPanelSections: [ diff --git a/superset/assets/javascripts/modules/colors.js b/superset/assets/javascripts/modules/colors.js index 7fd585f370030..8e3e521665464 100644 --- a/superset/assets/javascripts/modules/colors.js +++ b/superset/assets/javascripts/modules/colors.js @@ -1,5 +1,7 @@ import d3 from 'd3'; +export const brandColor = '#00A699'; + // Color related utility functions go in this object const bnbColors = [ '#ff5a5f', // rausch diff --git a/superset/assets/js_build.sh b/superset/assets/js_build.sh index 7e48caa126d7d..c71739888e983 100755 --- a/superset/assets/js_build.sh +++ b/superset/assets/js_build.sh @@ -5,7 +5,6 @@ npm --version node --version npm install -g yarn yarn -npm run sync-backend npm run lint npm run test npm run build diff --git a/superset/assets/package.json b/superset/assets/package.json index c74f66d139d4f..15cfff2a50bcd 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -40,6 +40,7 @@ "homepage": "http://superset.apache.org/", "dependencies": { "@data-ui/event-flow": "0.0.8", + "@data-ui/sparkline": "0.0.1", "babel-register": "^6.24.1", "bootstrap": "^3.3.6", "brace": "^0.10.0", @@ -56,12 +57,12 @@ "distributions": "^1.0.0", "immutable": "^3.8.2", "jed": "^1.1.1", - "po2json": "^0.4.5", "jquery": "3.1.1", "lodash.throttle": "^4.1.1", "moment": "^2.14.1", "mustache": "^2.2.1", "nvd3": "1.8.6", + "po2json": "^0.4.5", "prop-types": "^15.6.0", "react": "^15.6.2", "react-ace": "^5.0.1", @@ -70,8 +71,8 @@ "react-alert": "^1.0.14", "react-bootstrap": "^0.31.2", "react-bootstrap-table": "^4.0.2", - "react-datetime": "^2.9.0", "react-dom": "^15.6.2", + "react-datetime": "2.9.0", "react-gravatar": "^2.6.1", "react-grid-layout": "^0.14.4", "react-map-gl": "^3.0.4", @@ -79,6 +80,7 @@ "react-resizable": "^1.3.3", "react-select": "1.0.0-rc.3", "react-select-fast-filter-options": "^0.2.1", + "react-sortable-hoc": "^0.6.7", "react-split-pane": "^0.1.66", "react-syntax-highlighter": "^5.7.0", "react-virtualized": "^9.3.0", diff --git a/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx new file mode 100644 index 0000000000000..2f8cd6b913d50 --- /dev/null +++ b/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx @@ -0,0 +1,33 @@ +/* eslint-disable no-unused-expressions */ +import React from 'react'; +import { FormControl, OverlayTrigger } from 'react-bootstrap'; +import sinon from 'sinon'; +import { expect } from 'chai'; +import { describe, it, beforeEach } from 'mocha'; +import { shallow } from 'enzyme'; + +import TimeSeriesColumnControl from '../../../../javascripts/explore/components/controls/TimeSeriesColumnControl'; + +const defaultProps = { + name: 'x_axis_label', + label: 'X Axis Label', + onChange: sinon.spy(), +}; + +describe('SelectControl', () => { + let wrapper; + let inst; + beforeEach(() => { + wrapper = shallow(); + inst = wrapper.instance(); + }); + + it('renders an OverlayTrigger', () => { + expect(wrapper.find(OverlayTrigger)).to.have.lengthOf(1); + }); + + it('renders an Popover', () => { + const popOver = shallow(inst.renderPopover()); + expect(popOver.find(FormControl)).to.have.lengthOf(3); + }); +}); diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less index a4bb70a163fed..78f26bb68c13c 100644 --- a/superset/assets/stylesheets/superset.less +++ b/superset/assets/stylesheets/superset.less @@ -1,4 +1,5 @@ @import './less/index.less'; +@import "./less/cosmo/variables.less"; body { margin: 0px !important; @@ -364,6 +365,9 @@ iframe { .PopoverSection { padding-bottom: 10px; } +.popover { + max-width: 500px !important; +} .float-left { float: left; } @@ -382,3 +386,6 @@ g.annotation-container { stroke-width: 1; } } +.stroke-primary { + stroke: @brand-primary; +} diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js index d5c3abb1a7e68..dc5ee30516270 100644 --- a/superset/assets/visualizations/main.js +++ b/superset/assets/visualizations/main.js @@ -27,6 +27,7 @@ const vizMap = { separator: require('./markup.js'), sunburst: require('./sunburst.js'), table: require('./table.js'), + time_table: require('./time_table.jsx'), treemap: require('./treemap.js'), country_map: require('./country_map.js'), word_cloud: require('./word_cloud.js'), diff --git a/superset/assets/visualizations/time_table.css b/superset/assets/visualizations/time_table.css new file mode 100644 index 0000000000000..5f8a41bdd9e76 --- /dev/null +++ b/superset/assets/visualizations/time_table.css @@ -0,0 +1,3 @@ +.time-table { + overflow: auto; +} diff --git a/superset/assets/visualizations/time_table.jsx b/superset/assets/visualizations/time_table.jsx new file mode 100644 index 0000000000000..928352cfc7b10 --- /dev/null +++ b/superset/assets/visualizations/time_table.jsx @@ -0,0 +1,173 @@ +import ReactDOM from 'react-dom'; +import React from 'react'; +import propTypes from 'prop-types'; +import { Table, Thead, Th } from 'reactable'; +import d3 from 'd3'; +import { Sparkline, LineSeries, PointSeries } from '@data-ui/sparkline'; + +import MetricOption from '../javascripts/components/MetricOption'; +import TooltipWrapper from '../javascripts/components/TooltipWrapper'; +import { d3format, brandColor } from '../javascripts/modules/utils'; +import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger'; +import './time_table.css'; + +const SPARK_MARGIN = 3; +const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0']; + +function FormattedNumber({ num, format }) { + if (format) { + return ( + {d3format(format, num)} + ); + } + return {num}; +} +FormattedNumber.propTypes = { + num: propTypes.number.isRequired, + format: propTypes.string.isRequired, +}; + +function viz(slice, payload) { + slice.container.css('overflow', 'auto'); + slice.container.css('height', slice.height()); + const recs = payload.data.records; + const fd = payload.form_data; + const data = Object.keys(recs).sort().map((iso) => { + const o = recs[iso]; + return o; + }); + const reversedData = data.slice(); + reversedData.reverse(); + const metricMap = {}; + slice.datasource.metrics.forEach((m) => { + metricMap[m.metric_name] = m; + }); + + let metrics; + if (payload.data.is_group_by) { + // Sorting by first column desc + metrics = payload.data.columns.sort((m1, m2) => ( + reversedData[0][m1] > reversedData[0][m2] ? -1 : 1 + )); + } else { + // Using ordering specified in Metrics dropdown + metrics = payload.data.columns; + } + const tableData = metrics.map((metric) => { + let leftCell; + if (!payload.data.is_group_by) { + leftCell = ; + } else { + leftCell = metric; + } + const row = { metric: leftCell }; + fd.column_collection.forEach((c) => { + if (c.colType === 'spark') { + let sparkData; + if (!c.timeRatio) { + sparkData = data.map(d => d[metric]); + } else { + // Period ratio sparkline + sparkData = []; + for (let i = c.timeRatio; i < data.length; i++) { + sparkData.push(data[i][metric] / data[i - c.timeRatio][metric]); + } + } + const extent = d3.extent(data, d => d[metric]); + const tooltip = `min: ${extent[0]}, max: ${extent[1]}`; + row[c.key] = ( + +
+ + + + +
+
); + } else { + const recent = reversedData[0][metric]; + let v; + if (c.colType === 'time') { + // Time lag ratio + v = reversedData[parseInt(c.timeLag, 10)][metric]; + if (c.comparisonType === 'diff') { + v -= recent; + } else if (c.comparisonType === 'perc') { + v /= recent; + } else if (c.comparisonType === 'perc_change') { + v = (v / recent) - 1; + } + } else if (c.colType === 'contrib') { + // contribution to column total + v = recent / Object.keys(reversedData[0]) + .map(k => reversedData[0][k]) + .reduce((a, b) => a + b); + } else if (c.colType === 'avg') { + // Average over the last {timeLag} + v = reversedData + .map((k, i) => i < c.timeLag ? k[metric] : 0) + .reduce((a, b) => a + b) / c.timeLag; + } + let color; + if (c.bounds && c.bounds[0] !== null && c.bounds[1] !== null) { + const scaler = d3.scale.linear() + .domain([ + c.bounds[0], + c.bounds[0] + ((c.bounds[1] - c.bounds[0]) / 2), + c.bounds[1]]) + .range([ACCESSIBLE_COLOR_BOUNDS[0], 'grey', ACCESSIBLE_COLOR_BOUNDS[1]]); + color = scaler(v); + } else if (c.bounds && c.bounds[0] !== null) { + color = v >= c.bounds[0] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0]; + } else if (c.bounds && c.bounds[1] !== null) { + color = v < c.bounds[1] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0]; + } + row[c.key] = ( + + + ); + } + }); + return row; + }); + ReactDOM.render( + + + + {fd.column_collection.map((c, i) => ( + ))} + +
Metric + {c.label} {c.tooltip && ( + + )} +
, + document.getElementById(slice.containerId), + ); +} + +module.exports = viz; diff --git a/superset/viz.py b/superset/viz.py index 22d2ea9ec3776..2283e8daaccb1 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -10,12 +10,13 @@ import copy import hashlib +import inspect import logging import traceback import uuid import zlib -from collections import OrderedDict, defaultdict +from collections import defaultdict from itertools import product from datetime import datetime, timedelta @@ -422,6 +423,48 @@ def json_dumps(self, obj): return super(TableViz, self).json_dumps(obj) +class TimeTableViz(BaseViz): + + """A data table with rich time-series related columns""" + + viz_type = "time_table" + verbose_name = _("Time Table View") + credits = 'a
Superset original' + is_timeseries = True + + def query_obj(self): + d = super(TimeTableViz, self).query_obj() + fd = self.form_data + + if not fd.get('metrics'): + raise Exception(_("Pick at least one metric")) + + if fd.get('groupby') and len(fd.get('metrics')) > 1: + raise Exception(_( + "When using 'Group By' you are limited to use " + "a single metric")) + return d + + def get_data(self, df): + fd = self.form_data + values = self.metrics + columns = None + if fd.get('groupby'): + values = self.metrics[0] + columns = fd.get('groupby') + pt = df.pivot_table( + index=DTTM_ALIAS, + columns=columns, + values=values) + pt.index = pt.index.map(str) + pt = pt.sort_index() + return dict( + records=pt.to_dict(orient='index'), + columns=list(pt.columns), + is_group_by=len(fd.get('groupby')) > 0, + ) + + class PivotTableViz(BaseViz): """A pivot table view, define your rows, columns and metrics""" @@ -1669,6 +1712,7 @@ def get_data(self, df): "color": fd.get("mapbox_color"), } + class EventFlowViz(BaseViz): """A visualization to explore patterns in event sequences""" @@ -1684,7 +1728,8 @@ def query_obj(self): event_key = form_data.get('all_columns_x') entity_key = form_data.get('entity') meta_keys = [ - col for col in form_data.get('all_columns') if col != event_key and col != entity_key + col for col in form_data.get('all_columns') + if col != event_key and col != entity_key ] query['columns'] = [event_key, entity_key] + meta_keys @@ -1758,42 +1803,9 @@ def get_data(self, df): return data -viz_types_list = [ - TableViz, - PivotTableViz, - NVD3TimeSeriesViz, - NVD3DualLineViz, - NVD3CompareTimeSeriesViz, - NVD3TimeSeriesStackedViz, - NVD3TimeSeriesBarViz, - DistributionBarViz, - DistributionPieViz, - BubbleViz, - BulletViz, - MarkupViz, - WordCloudViz, - BigNumberViz, - BigNumberTotalViz, - SunburstViz, - DirectedForceViz, - SankeyViz, - CountryMapViz, - ChordViz, - WorldMapViz, - FilterBoxViz, - IFrameViz, - ParallelCoordinatesViz, - HeatmapViz, - BoxPlotViz, - TreemapViz, - CalHeatmapViz, - HorizonViz, - MapboxViz, - HistogramViz, - SeparatorViz, - EventFlowViz, - PairedTTestViz, -] - -viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list - if v.viz_type not in config.get('VIZ_TYPE_BLACKLIST')]) +viz_types = { + o.viz_type: o for o in globals().values() + if ( + inspect.isclass(o) and + issubclass(o, BaseViz) and + o.viz_type not in config.get('VIZ_TYPE_BLACKLIST'))}