Skip to content

Commit

Permalink
[time series table] visual improvements (#3957)
Browse files Browse the repository at this point in the history
* [time series table] visual improvements

* [time series table] don't set cell color if text color isn't set
  • Loading branch information
williaster authored and mistercrunch committed Dec 1, 2017
1 parent 9904593 commit 76a2f95
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -61,7 +61,7 @@ export default class TimeSeriesColumnControl extends React.Component {
return (
<Row style={{ marginTop: '5px' }}>
<Col md={5}>
{label}{' '}
{`${label} `}
<InfoTooltipWithTrigger
placement="top"
tooltip={tooltip}
Expand All @@ -75,7 +75,7 @@ export default class TimeSeriesColumnControl extends React.Component {
renderPopover() {
return (
<Popover id="ts-col-popo" title="Column Configuration">
<div style={{ width: '280px' }}>
<div style={{ width: 300 }}>
{this.formRow(
'Label',
'The column header label',
Expand Down Expand Up @@ -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',
<BoundsControl
Expand All @@ -181,14 +179,25 @@ export default class TimeSeriesColumnControl extends React.Component {
/>,
)}
{this.formRow(
'D3 format',
'D3 format string',
'Number format',
'Optional d3 number format string',
'd3-format',
<FormControl
value={this.state.d3format}
onChange={this.onTextInputChange.bind(this, 'd3format')}
bsSize="small"
placeholder="D3 format string"
placeholder="Number format string"
/>,
)}
{this.state.colType === 'spark' && this.formRow(
'Date format',
'Optional d3 date format string',
'date-format',
<FormControl
value={this.state.dateFormat}
onChange={this.onTextInputChange.bind(this, 'dateFormat')}
bsSize="small"
placeholder="Date format string"
/>,
)}
</div>
Expand Down
17 changes: 15 additions & 2 deletions superset/assets/javascripts/modules/dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
4 changes: 2 additions & 2 deletions superset/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions superset/assets/stylesheets/superset.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
91 changes: 54 additions & 37 deletions superset/assets/visualizations/time_table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 }) {
Expand Down Expand Up @@ -65,47 +72,50 @@ function viz(slice, payload) {
leftCell = url ? <a href={url} target="_blank">{metric}</a> : 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 {
sparkData.push(null);
}
}
}
row[c.key] = {
const formatDate = formatDateThunk(column.dateFormat);
row[column.key] = {
data: sparkData[sparkData.length - 1],
display: (
<WithTooltip
tooltipProps={sparklineTooltipProps}
hoverStyles={null}
renderTooltip={({ index }) => (
<div>
<strong>{d3format(c.d3format, sparkData[index])}</strong>
<strong>{d3format(column.d3format, sparkData[index])}</strong>
<div>{formatDate(data[index].iso)}</div>
</div>
)}
>
{({ onMouseLeave, onMouseMove, tooltipData }) => (
<Sparkline
ariaLabel={`spark-${metric}`}
width={parseInt(c.width, 10) || 300}
height={parseInt(c.height, 10) || 50}
width={parseInt(column.width, 10) || 300}
height={parseInt(column.height, 10) || 50}
margin={SPARKLINE_MARGIN}
data={sparkData}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
>
<LineSeries
showArea={false}
stroke={brandColor}
stroke="#767676"
/>
{tooltipData &&
<VerticalReferenceLine
Expand All @@ -116,7 +126,7 @@ function viz(slice, payload) {
{tooltipData &&
<PointSeries
points={[tooltipData.index]}
fill={brandColor}
fill="#767676"
strokeWidth={1}
/>}
</Sparkline>
Expand All @@ -127,55 +137,61 @@ 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: (
<span style={{ color }}>
<FormattedNumber num={v} format={c.d3format} />
</span>),
<div style={{ color }}>
<FormattedNumber num={v} format={column.d3format} />
</div>
),
style: color && {
boxShadow: `inset 0px -2.5px 0px 0px ${color}`,
borderRight: '2px solid #fff',
},
};
}
});
return row;
});
ReactDOM.render(
<Table
className="table table-condensed"
className="table table-no-hover"
defaultSort={defaultSort}
sortBy={defaultSort}
sortable={fd.column_collection.map(c => c.key)}
Expand All @@ -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}
</Td>))}
Expand Down

0 comments on commit 76a2f95

Please sign in to comment.