mirror of https://github.com/apache/superset.git
[time series table] visual improvements (#3957)
* [time series table] visual improvements * [time series table] don't set cell color if text color isn't set
This commit is contained in:
parent
9904593dc3
commit
76a2f95231
|
@ -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 (
|
||||
<Row style={{ marginTop: '5px' }}>
|
||||
<Col md={5}>
|
||||
{label}{' '}
|
||||
{`${label} `}
|
||||
<InfoTooltipWithTrigger
|
||||
placement="top"
|
||||
tooltip={tooltip}
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 ? <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 {
|
||||
|
@ -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: (
|
||||
<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>
|
||||
)}
|
||||
|
@ -96,8 +106,8 @@ function viz(slice, payload) {
|
|||
{({ 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}
|
||||
|
@ -105,7 +115,7 @@ function viz(slice, payload) {
|
|||
>
|
||||
<LineSeries
|
||||
showArea={false}
|
||||
stroke={brandColor}
|
||||
stroke="#767676"
|
||||
/>
|
||||
{tooltipData &&
|
||||
<VerticalReferenceLine
|
||||
|
@ -116,7 +126,7 @@ function viz(slice, payload) {
|
|||
{tooltipData &&
|
||||
<PointSeries
|
||||
points={[tooltipData.index]}
|
||||
fill={brandColor}
|
||||
fill="#767676"
|
||||
strokeWidth={1}
|
||||
/>}
|
||||
</Sparkline>
|
||||
|
@ -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: (
|
||||
<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',
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -175,7 +191,7 @@ function viz(slice, payload) {
|
|||
});
|
||||
ReactDOM.render(
|
||||
<Table
|
||||
className="table table-condensed"
|
||||
className="table table-no-hover"
|
||||
defaultSort={defaultSort}
|
||||
sortBy={defaultSort}
|
||||
sortable={fd.column_collection.map(c => 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}
|
||||
</Td>))}
|
||||
|
|
Loading…
Reference in New Issue