diff --git a/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx b/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx index cd8ec98c22..2634dd77b5 100644 --- a/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx +++ b/superset/assets/javascripts/explore/components/controls/TimeSeriesColumnControl.jsx @@ -33,7 +33,7 @@ const colTypeOptions = [ export default class TimeSeriesColumnControl extends React.Component { constructor(props) { super(props); - const state = Object.assign({}, props); + const state = { ...props }; delete state.onChange; this.state = state; this.onChange = this.onChange.bind(this); @@ -61,7 +61,7 @@ export default class TimeSeriesColumnControl extends React.Component { return ( - {label}{' '} + {`${label} `} -
+
{this.formRow( 'Label', 'The column header label', @@ -166,13 +166,11 @@ export default class TimeSeriesColumnControl extends React.Component { />, )} {this.state.colType !== 'spark' && this.formRow( - 'Bounds', + 'Color 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.' + `Number bounds used for color encoding from red to blue. + Reverse the numbers for blue to red. To get pure red or blue, + you can enter either only min or max.` ), 'bounds', , )} {this.formRow( - 'D3 format', - 'D3 format string', + 'Number format', + 'Optional d3 number format string', 'd3-format', , + )} + {this.state.colType === 'spark' && this.formRow( + 'Date format', + 'Optional d3 date format string', + 'date-format', + , )}
diff --git a/superset/assets/javascripts/modules/dates.js b/superset/assets/javascripts/modules/dates.js index 88b62c5890..c1bf47d79b 100644 --- a/superset/assets/javascripts/modules/dates.js +++ b/superset/assets/javascripts/modules/dates.js @@ -66,10 +66,23 @@ export const formatDate = function (dttm) { const d = UTC(new Date(dttm)); return tickMultiFormat(d); }; -export const fDuration = function (t1, t2, f = 'HH:mm:ss.SS') { + +export const formatDateThunk = function (format) { + if (!format) { + return formatDate; + } + + const formatter = d3.time.format(format); + return (dttm) => { + const d = UTC(new Date(dttm)); + return formatter(d); + }; +}; + +export const fDuration = function (t1, t2, format = 'HH:mm:ss.SS') { const diffSec = t2 - t1; const duration = moment(new Date(diffSec)); - return duration.utc().format(f); + return duration.utc().format(format); }; export const now = function () { diff --git a/superset/assets/package.json b/superset/assets/package.json index c3c217437a..eb3af5514c 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -39,8 +39,8 @@ }, "homepage": "http://superset.apache.org/", "dependencies": { - "@data-ui/event-flow": "0.0.8", - "@data-ui/sparkline": "0.0.47", + "@data-ui/event-flow": "^0.0.8", + "@data-ui/sparkline": "^0.0.49", "babel-register": "^6.24.1", "bootstrap": "^3.3.6", "brace": "^0.10.0", diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less index 49c65448a5..6f6b50282c 100644 --- a/superset/assets/stylesheets/superset.less +++ b/superset/assets/stylesheets/superset.less @@ -218,6 +218,10 @@ div.widget { float: left; } +table.table-no-hover tr:hover { + background-color: initial; +} + .editable-title input { padding: 2px 6px 3px 6px; } diff --git a/superset/assets/visualizations/time_table.jsx b/superset/assets/visualizations/time_table.jsx index cb145c8a1b..0347c4148f 100644 --- a/superset/assets/visualizations/time_table.jsx +++ b/superset/assets/visualizations/time_table.jsx @@ -7,8 +7,8 @@ import Mustache from 'mustache'; import { Sparkline, LineSeries, PointSeries, VerticalReferenceLine, WithTooltip } from '@data-ui/sparkline'; import MetricOption from '../javascripts/components/MetricOption'; -import { d3format, brandColor } from '../javascripts/modules/utils'; -import { formatDate } from '../javascripts/modules/dates'; +import { d3format } from '../javascripts/modules/utils'; +import { formatDateThunk } from '../javascripts/modules/dates'; import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger'; import './time_table.css'; @@ -18,6 +18,13 @@ const SPARKLINE_MARGIN = { bottom: 8, left: 8, }; +const sparklineTooltipProps = { + style: { + opacity: 0.8, + }, + offsetTop: 0, +}; + const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0']; function FormattedNumber({ num, format }) { @@ -65,16 +72,16 @@ function viz(slice, payload) { leftCell = url ? {metric} : metric; } const row = { metric: leftCell }; - fd.column_collection.forEach((c) => { - if (c.colType === 'spark') { + fd.column_collection.forEach((column) => { + if (column.colType === 'spark') { let sparkData; - if (!c.timeRatio) { + if (!column.timeRatio) { sparkData = data.map(d => d[metric]); } else { // Period ratio sparkline sparkData = []; - for (let i = c.timeRatio; i < data.length; i++) { - const prevData = data[i - c.timeRatio][metric]; + for (let i = column.timeRatio; i < data.length; i++) { + const prevData = data[i - column.timeRatio][metric]; if (prevData && prevData !== 0) { sparkData.push(data[i][metric] / prevData); } else { @@ -82,13 +89,16 @@ function viz(slice, payload) { } } } - row[c.key] = { + const formatDate = formatDateThunk(column.dateFormat); + row[column.key] = { data: sparkData[sparkData.length - 1], display: ( (
- {d3format(c.d3format, sparkData[index])} + {d3format(column.d3format, sparkData[index])}
{formatDate(data[index].iso)}
)} @@ -96,8 +106,8 @@ function viz(slice, payload) { {({ onMouseLeave, onMouseMove, tooltipData }) => ( {tooltipData && } @@ -127,47 +137,53 @@ function viz(slice, payload) { } else { const recent = reversedData[0][metric]; let v; - if (c.colType === 'time') { + if (column.colType === 'time') { // Time lag ratio - v = reversedData[parseInt(c.timeLag, 10)][metric]; - if (c.comparisonType === 'diff') { + v = reversedData[parseInt(column.timeLag, 10)][metric]; + if (column.comparisonType === 'diff') { v = recent - v; - } else if (c.comparisonType === 'perc') { + } else if (column.comparisonType === 'perc') { v = recent / v; - } else if (c.comparisonType === 'perc_change') { + } else if (column.comparisonType === 'perc_change') { v = (recent / v) - 1; } - } else if (c.colType === 'contrib') { + } else if (column.colType === 'contrib') { // contribution to column total v = recent / Object.keys(reversedData[0]) - .map(k => k !== 'iso' ? reversedData[0][k] : null) - .reduce((a, b) => a + b); - } else if (c.colType === 'avg') { + .map(k => k !== 'iso' ? reversedData[0][k] : null) + .reduce((a, b) => a + b); + } else if (column.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; + .map((k, i) => i < column.timeLag ? k[metric] : 0) + .reduce((a, b) => a + b) / column.timeLag; } let color; - if (c.bounds && c.bounds[0] !== null && c.bounds[1] !== null) { + if (column.bounds && column.bounds[0] !== null && column.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]]) + column.bounds[0], + column.bounds[0] + ((column.bounds[1] - column.bounds[0]) / 2), + column.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]; + } else if (column.bounds && column.bounds[0] !== null) { + color = v >= column.bounds[0] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0]; + } else if (column.bounds && column.bounds[1] !== null) { + color = v < column.bounds[1] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0]; } - row[c.key] = { + row[column.key] = { data: v, display: ( - - - ), +
+ +
+ ), + style: color && { + boxShadow: `inset 0px -2.5px 0px 0px ${color}`, + borderRight: '2px solid #fff', + }, }; } }); @@ -175,7 +191,7 @@ function viz(slice, payload) { }); ReactDOM.render( c.key)} @@ -201,6 +217,7 @@ function viz(slice, payload) { column={c.key} key={c.key} value={row[c.key].data} + style={row[c.key].style} > {row[c.key].display} ))}