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 {
|
export default class TimeSeriesColumnControl extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const state = Object.assign({}, props);
|
const state = { ...props };
|
||||||
delete state.onChange;
|
delete state.onChange;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
|
@ -61,7 +61,7 @@ export default class TimeSeriesColumnControl extends React.Component {
|
||||||
return (
|
return (
|
||||||
<Row style={{ marginTop: '5px' }}>
|
<Row style={{ marginTop: '5px' }}>
|
||||||
<Col md={5}>
|
<Col md={5}>
|
||||||
{label}{' '}
|
{`${label} `}
|
||||||
<InfoTooltipWithTrigger
|
<InfoTooltipWithTrigger
|
||||||
placement="top"
|
placement="top"
|
||||||
tooltip={tooltip}
|
tooltip={tooltip}
|
||||||
|
@ -75,7 +75,7 @@ export default class TimeSeriesColumnControl extends React.Component {
|
||||||
renderPopover() {
|
renderPopover() {
|
||||||
return (
|
return (
|
||||||
<Popover id="ts-col-popo" title="Column Configuration">
|
<Popover id="ts-col-popo" title="Column Configuration">
|
||||||
<div style={{ width: '280px' }}>
|
<div style={{ width: 300 }}>
|
||||||
{this.formRow(
|
{this.formRow(
|
||||||
'Label',
|
'Label',
|
||||||
'The column header label',
|
'The column header label',
|
||||||
|
@ -166,13 +166,11 @@ export default class TimeSeriesColumnControl extends React.Component {
|
||||||
/>,
|
/>,
|
||||||
)}
|
)}
|
||||||
{this.state.colType !== 'spark' && this.formRow(
|
{this.state.colType !== 'spark' && this.formRow(
|
||||||
'Bounds',
|
'Color bounds',
|
||||||
(
|
(
|
||||||
'Number bounds used for color coding from red to green. ' +
|
`Number bounds used for color encoding from red to blue.
|
||||||
'Reverse the number for green to red. To get boolean ' +
|
Reverse the numbers for blue to red. To get pure red or blue,
|
||||||
'red or green without spectrum, you can use either only ' +
|
you can enter either only min or max.`
|
||||||
'min, or max, depending on whether small or big should be ' +
|
|
||||||
'green or red.'
|
|
||||||
),
|
),
|
||||||
'bounds',
|
'bounds',
|
||||||
<BoundsControl
|
<BoundsControl
|
||||||
|
@ -181,14 +179,25 @@ export default class TimeSeriesColumnControl extends React.Component {
|
||||||
/>,
|
/>,
|
||||||
)}
|
)}
|
||||||
{this.formRow(
|
{this.formRow(
|
||||||
'D3 format',
|
'Number format',
|
||||||
'D3 format string',
|
'Optional d3 number format string',
|
||||||
'd3-format',
|
'd3-format',
|
||||||
<FormControl
|
<FormControl
|
||||||
value={this.state.d3format}
|
value={this.state.d3format}
|
||||||
onChange={this.onTextInputChange.bind(this, 'd3format')}
|
onChange={this.onTextInputChange.bind(this, 'd3format')}
|
||||||
bsSize="small"
|
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>
|
</div>
|
||||||
|
|
|
@ -66,10 +66,23 @@ export const formatDate = function (dttm) {
|
||||||
const d = UTC(new Date(dttm));
|
const d = UTC(new Date(dttm));
|
||||||
return tickMultiFormat(d);
|
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 diffSec = t2 - t1;
|
||||||
const duration = moment(new Date(diffSec));
|
const duration = moment(new Date(diffSec));
|
||||||
return duration.utc().format(f);
|
return duration.utc().format(format);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const now = function () {
|
export const now = function () {
|
||||||
|
|
|
@ -39,8 +39,8 @@
|
||||||
},
|
},
|
||||||
"homepage": "http://superset.apache.org/",
|
"homepage": "http://superset.apache.org/",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@data-ui/event-flow": "0.0.8",
|
"@data-ui/event-flow": "^0.0.8",
|
||||||
"@data-ui/sparkline": "0.0.47",
|
"@data-ui/sparkline": "^0.0.49",
|
||||||
"babel-register": "^6.24.1",
|
"babel-register": "^6.24.1",
|
||||||
"bootstrap": "^3.3.6",
|
"bootstrap": "^3.3.6",
|
||||||
"brace": "^0.10.0",
|
"brace": "^0.10.0",
|
||||||
|
|
|
@ -218,6 +218,10 @@ div.widget {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.table-no-hover tr:hover {
|
||||||
|
background-color: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.editable-title input {
|
.editable-title input {
|
||||||
padding: 2px 6px 3px 6px;
|
padding: 2px 6px 3px 6px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import Mustache from 'mustache';
|
||||||
import { Sparkline, LineSeries, PointSeries, VerticalReferenceLine, WithTooltip } from '@data-ui/sparkline';
|
import { Sparkline, LineSeries, PointSeries, VerticalReferenceLine, WithTooltip } from '@data-ui/sparkline';
|
||||||
|
|
||||||
import MetricOption from '../javascripts/components/MetricOption';
|
import MetricOption from '../javascripts/components/MetricOption';
|
||||||
import { d3format, brandColor } from '../javascripts/modules/utils';
|
import { d3format } from '../javascripts/modules/utils';
|
||||||
import { formatDate } from '../javascripts/modules/dates';
|
import { formatDateThunk } from '../javascripts/modules/dates';
|
||||||
import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger';
|
import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger';
|
||||||
import './time_table.css';
|
import './time_table.css';
|
||||||
|
|
||||||
|
@ -18,6 +18,13 @@ const SPARKLINE_MARGIN = {
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
left: 8,
|
left: 8,
|
||||||
};
|
};
|
||||||
|
const sparklineTooltipProps = {
|
||||||
|
style: {
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
offsetTop: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];
|
const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];
|
||||||
|
|
||||||
function FormattedNumber({ num, format }) {
|
function FormattedNumber({ num, format }) {
|
||||||
|
@ -65,16 +72,16 @@ function viz(slice, payload) {
|
||||||
leftCell = url ? <a href={url} target="_blank">{metric}</a> : metric;
|
leftCell = url ? <a href={url} target="_blank">{metric}</a> : metric;
|
||||||
}
|
}
|
||||||
const row = { metric: leftCell };
|
const row = { metric: leftCell };
|
||||||
fd.column_collection.forEach((c) => {
|
fd.column_collection.forEach((column) => {
|
||||||
if (c.colType === 'spark') {
|
if (column.colType === 'spark') {
|
||||||
let sparkData;
|
let sparkData;
|
||||||
if (!c.timeRatio) {
|
if (!column.timeRatio) {
|
||||||
sparkData = data.map(d => d[metric]);
|
sparkData = data.map(d => d[metric]);
|
||||||
} else {
|
} else {
|
||||||
// Period ratio sparkline
|
// Period ratio sparkline
|
||||||
sparkData = [];
|
sparkData = [];
|
||||||
for (let i = c.timeRatio; i < data.length; i++) {
|
for (let i = column.timeRatio; i < data.length; i++) {
|
||||||
const prevData = data[i - c.timeRatio][metric];
|
const prevData = data[i - column.timeRatio][metric];
|
||||||
if (prevData && prevData !== 0) {
|
if (prevData && prevData !== 0) {
|
||||||
sparkData.push(data[i][metric] / prevData);
|
sparkData.push(data[i][metric] / prevData);
|
||||||
} else {
|
} 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],
|
data: sparkData[sparkData.length - 1],
|
||||||
display: (
|
display: (
|
||||||
<WithTooltip
|
<WithTooltip
|
||||||
|
tooltipProps={sparklineTooltipProps}
|
||||||
|
hoverStyles={null}
|
||||||
renderTooltip={({ index }) => (
|
renderTooltip={({ index }) => (
|
||||||
<div>
|
<div>
|
||||||
<strong>{d3format(c.d3format, sparkData[index])}</strong>
|
<strong>{d3format(column.d3format, sparkData[index])}</strong>
|
||||||
<div>{formatDate(data[index].iso)}</div>
|
<div>{formatDate(data[index].iso)}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -96,8 +106,8 @@ function viz(slice, payload) {
|
||||||
{({ onMouseLeave, onMouseMove, tooltipData }) => (
|
{({ onMouseLeave, onMouseMove, tooltipData }) => (
|
||||||
<Sparkline
|
<Sparkline
|
||||||
ariaLabel={`spark-${metric}`}
|
ariaLabel={`spark-${metric}`}
|
||||||
width={parseInt(c.width, 10) || 300}
|
width={parseInt(column.width, 10) || 300}
|
||||||
height={parseInt(c.height, 10) || 50}
|
height={parseInt(column.height, 10) || 50}
|
||||||
margin={SPARKLINE_MARGIN}
|
margin={SPARKLINE_MARGIN}
|
||||||
data={sparkData}
|
data={sparkData}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
|
@ -105,7 +115,7 @@ function viz(slice, payload) {
|
||||||
>
|
>
|
||||||
<LineSeries
|
<LineSeries
|
||||||
showArea={false}
|
showArea={false}
|
||||||
stroke={brandColor}
|
stroke="#767676"
|
||||||
/>
|
/>
|
||||||
{tooltipData &&
|
{tooltipData &&
|
||||||
<VerticalReferenceLine
|
<VerticalReferenceLine
|
||||||
|
@ -116,7 +126,7 @@ function viz(slice, payload) {
|
||||||
{tooltipData &&
|
{tooltipData &&
|
||||||
<PointSeries
|
<PointSeries
|
||||||
points={[tooltipData.index]}
|
points={[tooltipData.index]}
|
||||||
fill={brandColor}
|
fill="#767676"
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
/>}
|
/>}
|
||||||
</Sparkline>
|
</Sparkline>
|
||||||
|
@ -127,47 +137,53 @@ function viz(slice, payload) {
|
||||||
} else {
|
} else {
|
||||||
const recent = reversedData[0][metric];
|
const recent = reversedData[0][metric];
|
||||||
let v;
|
let v;
|
||||||
if (c.colType === 'time') {
|
if (column.colType === 'time') {
|
||||||
// Time lag ratio
|
// Time lag ratio
|
||||||
v = reversedData[parseInt(c.timeLag, 10)][metric];
|
v = reversedData[parseInt(column.timeLag, 10)][metric];
|
||||||
if (c.comparisonType === 'diff') {
|
if (column.comparisonType === 'diff') {
|
||||||
v = recent - v;
|
v = recent - v;
|
||||||
} else if (c.comparisonType === 'perc') {
|
} else if (column.comparisonType === 'perc') {
|
||||||
v = recent / v;
|
v = recent / v;
|
||||||
} else if (c.comparisonType === 'perc_change') {
|
} else if (column.comparisonType === 'perc_change') {
|
||||||
v = (recent / v) - 1;
|
v = (recent / v) - 1;
|
||||||
}
|
}
|
||||||
} else if (c.colType === 'contrib') {
|
} else if (column.colType === 'contrib') {
|
||||||
// contribution to column total
|
// contribution to column total
|
||||||
v = recent / Object.keys(reversedData[0])
|
v = recent / Object.keys(reversedData[0])
|
||||||
.map(k => k !== 'iso' ? reversedData[0][k] : null)
|
.map(k => k !== 'iso' ? reversedData[0][k] : null)
|
||||||
.reduce((a, b) => a + b);
|
.reduce((a, b) => a + b);
|
||||||
} else if (c.colType === 'avg') {
|
} else if (column.colType === 'avg') {
|
||||||
// Average over the last {timeLag}
|
// Average over the last {timeLag}
|
||||||
v = reversedData
|
v = reversedData
|
||||||
.map((k, i) => i < c.timeLag ? k[metric] : 0)
|
.map((k, i) => i < column.timeLag ? k[metric] : 0)
|
||||||
.reduce((a, b) => a + b) / c.timeLag;
|
.reduce((a, b) => a + b) / column.timeLag;
|
||||||
}
|
}
|
||||||
let color;
|
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()
|
const scaler = d3.scale.linear()
|
||||||
.domain([
|
.domain([
|
||||||
c.bounds[0],
|
column.bounds[0],
|
||||||
c.bounds[0] + ((c.bounds[1] - c.bounds[0]) / 2),
|
column.bounds[0] + ((column.bounds[1] - column.bounds[0]) / 2),
|
||||||
c.bounds[1]])
|
column.bounds[1],
|
||||||
|
])
|
||||||
.range([ACCESSIBLE_COLOR_BOUNDS[0], 'grey', ACCESSIBLE_COLOR_BOUNDS[1]]);
|
.range([ACCESSIBLE_COLOR_BOUNDS[0], 'grey', ACCESSIBLE_COLOR_BOUNDS[1]]);
|
||||||
color = scaler(v);
|
color = scaler(v);
|
||||||
} else if (c.bounds && c.bounds[0] !== null) {
|
} else if (column.bounds && column.bounds[0] !== null) {
|
||||||
color = v >= c.bounds[0] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0];
|
color = v >= column.bounds[0] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0];
|
||||||
} else if (c.bounds && c.bounds[1] !== null) {
|
} else if (column.bounds && column.bounds[1] !== null) {
|
||||||
color = v < c.bounds[1] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0];
|
color = v < column.bounds[1] ? ACCESSIBLE_COLOR_BOUNDS[1] : ACCESSIBLE_COLOR_BOUNDS[0];
|
||||||
}
|
}
|
||||||
row[c.key] = {
|
row[column.key] = {
|
||||||
data: v,
|
data: v,
|
||||||
display: (
|
display: (
|
||||||
<span style={{ color }}>
|
<div style={{ color }}>
|
||||||
<FormattedNumber num={v} format={c.d3format} />
|
<FormattedNumber num={v} format={column.d3format} />
|
||||||
</span>),
|
</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(
|
ReactDOM.render(
|
||||||
<Table
|
<Table
|
||||||
className="table table-condensed"
|
className="table table-no-hover"
|
||||||
defaultSort={defaultSort}
|
defaultSort={defaultSort}
|
||||||
sortBy={defaultSort}
|
sortBy={defaultSort}
|
||||||
sortable={fd.column_collection.map(c => c.key)}
|
sortable={fd.column_collection.map(c => c.key)}
|
||||||
|
@ -201,6 +217,7 @@ function viz(slice, payload) {
|
||||||
column={c.key}
|
column={c.key}
|
||||||
key={c.key}
|
key={c.key}
|
||||||
value={row[c.key].data}
|
value={row[c.key].data}
|
||||||
|
style={row[c.key].style}
|
||||||
>
|
>
|
||||||
{row[c.key].display}
|
{row[c.key].display}
|
||||||
</Td>))}
|
</Td>))}
|
||||||
|
|
Loading…
Reference in New Issue