Skip to content

Commit

Permalink
Merge pull request apache#83 from kristw/kristw/sparkline
Browse files Browse the repository at this point in the history
Cherry-pick(s): allow min/max value , show y-axis for sparklines in time series table
  • Loading branch information
kristw authored Aug 14, 2018
2 parents 42bdef3 + 1577548 commit de73024
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Select from 'react-select';

import InfoTooltipWithTrigger from '../../../components/InfoTooltipWithTrigger';
import BoundsControl from './BoundsControl';
import CheckboxControl from './CheckboxControl';

const propTypes = {
onChange: PropTypes.func,
Expand Down Expand Up @@ -47,9 +48,15 @@ export default class TimeSeriesColumnControl extends React.Component {
onTextInputChange(attr, event) {
this.setState({ [attr]: event.target.value }, this.onChange);
}
onCheckboxChange(attr, value) {
this.setState({ [attr]: value }, this.onChange);
}
onBoundsChange(bounds) {
this.setState({ bounds }, this.onChange);
}
onYAxisBoundsChange(yAxisBounds) {
this.setState({ yAxisBounds }, this.onChange);
}
setType() {
}
textSummary() {
Expand Down Expand Up @@ -165,6 +172,28 @@ export default class TimeSeriesColumnControl extends React.Component {
options={comparisonTypeOptions}
/>,
)}
{this.state.colType === 'spark' && this.formRow(
'Show Y-axis',
(
'Show Y-axis on the sparkline. Will display the manually set min/max if set or min/max values in the data otherwise.'
),
'show-y-axis-bounds',
<CheckboxControl
value={this.state.showYAxis}
onChange={this.onCheckboxChange.bind(this, 'showYAxis')}
/>,
)}
{this.state.colType === 'spark' && this.formRow(
'Y-axis bounds',
(
'Manually set min/max values for the y-axis.'
),
'y-axis-bounds',
<BoundsControl
value={this.state.yAxisBounds}
onChange={this.onYAxisBoundsChange.bind(this)}
/>,
)}
{this.state.colType !== 'spark' && this.formRow(
'Color bounds',
(
Expand Down
2 changes: 1 addition & 1 deletion superset/assets/src/modules/visUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function getTextDimension({
}

if (isDefined(style)) {
['font', 'fontWeight', 'fontStyle', 'fontSize', 'fontFamily']
['font', 'fontWeight', 'fontStyle', 'fontSize', 'fontFamily', 'letterSpacing']
.filter(field => isDefined(style[field]))
.forEach((field) => {
textNode.style[field] = style[field];
Expand Down
173 changes: 173 additions & 0 deletions superset/assets/src/visualizations/SparklineCell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Sparkline, LineSeries, PointSeries, HorizontalReferenceLine, VerticalReferenceLine, WithTooltip } from '@data-ui/sparkline';
import { d3format } from '../modules/utils';
import { getTextDimension } from '../modules/visUtils';

const propTypes = {
className: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
data: PropTypes.array.isRequired,
ariaLabel: PropTypes.string,
numberFormat: PropTypes.string,
yAxisBounds: PropTypes.array,
showYAxis: PropTypes.bool,
renderTooltip: PropTypes.func,
};
const defaultProps = {
className: '',
width: 300,
height: 50,
ariaLabel: '',
numberFormat: undefined,
yAxisBounds: [null, null],
showYAxis: false,
renderTooltip() { return <div />; },
};

const MARGIN = {
top: 8,
right: 8,
bottom: 8,
left: 8,
};
const tooltipProps = {
style: {
opacity: 0.8,
},
offsetTop: 0,
};

function getSparklineTextWidth(text) {
return getTextDimension({
text,
style: {
fontSize: '12px',
fontWeight: 200,
letterSpacing: 0.4,
},
}).width + 5;
}

function isValidBoundValue(value) {
return value !== null && value !== undefined && value !== '' && !Number.isNaN(value);
}

class SparklineCell extends React.Component {
renderHorizontalReferenceLine(value, label) {
return (
<HorizontalReferenceLine
reference={value}
labelPosition="right"
renderLabel={() => label}
stroke="#bbb"
strokeDasharray="3 3"
strokeWidth={1}
/>
);
}

render() {
const {
width,
height,
data,
ariaLabel,
numberFormat,
yAxisBounds,
showYAxis,
renderTooltip,
} = this.props;

const yScale = {};
let hasMinBound = false;
let hasMaxBound = false;

if (yAxisBounds) {
const [minBound, maxBound] = yAxisBounds;
hasMinBound = isValidBoundValue(minBound);
if (hasMinBound) {
yScale.min = minBound;
}
hasMaxBound = isValidBoundValue(maxBound);
if (hasMaxBound) {
yScale.max = maxBound;
}
}

let min;
let max;
let minLabel;
let maxLabel;
let labelLength = 0;
if (showYAxis) {
const [minBound, maxBound] = yAxisBounds;
min = hasMinBound
? minBound
: data.reduce((acc, current) => Math.min(acc, current), data[0]);
max = hasMaxBound
? maxBound
: data.reduce((acc, current) => Math.max(acc, current), data[0]);

minLabel = d3format(numberFormat, min);
maxLabel = d3format(numberFormat, max);
labelLength = Math.max(
getSparklineTextWidth(minLabel),
getSparklineTextWidth(maxLabel),
);
}

const margin = {
...MARGIN,
right: MARGIN.right + labelLength,
};

return (
<WithTooltip
tooltipProps={tooltipProps}
hoverStyles={null}
renderTooltip={renderTooltip}
>
{({ onMouseLeave, onMouseMove, tooltipData }) => (
<Sparkline
ariaLabel={ariaLabel}
width={width}
height={height}
margin={margin}
data={data}
onMouseLeave={onMouseLeave}
onMouseMove={onMouseMove}
{...yScale}
>
{showYAxis &&
this.renderHorizontalReferenceLine(min, minLabel)}
{showYAxis &&
this.renderHorizontalReferenceLine(max, maxLabel)}
<LineSeries
showArea={false}
stroke="#767676"
/>
{tooltipData &&
<VerticalReferenceLine
reference={tooltipData.index}
strokeDasharray="3 3"
strokeWidth={1}
/>}
{tooltipData &&
<PointSeries
points={[tooltipData.index]}
fill="#767676"
strokeWidth={1}
/>}
</Sparkline>
)}
</WithTooltip>
);
}
}

SparklineCell.propTypes = propTypes;
SparklineCell.defaultProps = defaultProps;

export default SparklineCell;
70 changes: 18 additions & 52 deletions superset/assets/src/visualizations/time_table.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
import ReactDOM from 'react-dom';
import React from 'react';
import propTypes from 'prop-types';
import PropTypes from 'prop-types';
import { Table, Thead, Th, Tr, Td } from 'reactable';
import d3 from 'd3';
import Mustache from 'mustache';
import { Sparkline, LineSeries, PointSeries, VerticalReferenceLine, WithTooltip } from '@data-ui/sparkline';

import MetricOption from '../components/MetricOption';
import { d3format } from '../modules/utils';
import { formatDateThunk } from '../modules/dates';
import { d3format } from '../modules/utils';
import InfoTooltipWithTrigger from '../components/InfoTooltipWithTrigger';
import SparklineCell from './SparklineCell';
import './time_table.css';

const SPARKLINE_MARGIN = {
top: 8,
right: 8,
bottom: 8,
left: 8,
};
const sparklineTooltipProps = {
style: {
opacity: 0.8,
},
offsetTop: 0,
};

const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];

function FormattedNumber({ num, format }) {
Expand All @@ -37,8 +24,8 @@ function FormattedNumber({ num, format }) {
}

FormattedNumber.propTypes = {
num: propTypes.number,
format: propTypes.string,
num: PropTypes.number,
format: PropTypes.string,
};

function viz(slice, payload) {
Expand Down Expand Up @@ -93,49 +80,27 @@ function viz(slice, payload) {
}
}
}

const formatDate = formatDateThunk(column.dateFormat);

row[column.key] = {
data: sparkData[sparkData.length - 1],
display: (
<WithTooltip
tooltipProps={sparklineTooltipProps}
hoverStyles={null}
<SparklineCell
width={parseInt(column.width, 10) || 300}
height={parseInt(column.height, 10) || 50}
data={sparkData}
ariaLabel={`spark-${metricLabel}`}
numberFormat={column.d3format}
yAxisBounds={column.yAxisBounds}
showYAxis={column.showYAxis}
renderTooltip={({ index }) => (
<div>
<strong>{d3format(column.d3format, sparkData[index])}</strong>
<strong>{d3format(column.d3Format, sparkData[index])}</strong>
<div>{formatDate(data[index].iso)}</div>
</div>
)}
>
{({ onMouseLeave, onMouseMove, tooltipData }) => (
<Sparkline
ariaLabel={`spark-${metricLabel}`}
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="#767676"
/>
{tooltipData &&
<VerticalReferenceLine
reference={tooltipData.index}
strokeDasharray="3 3"
strokeWidth={1}
/>}
{tooltipData &&
<PointSeries
points={[tooltipData.index]}
fill="#767676"
strokeWidth={1}
/>}
</Sparkline>
)}
</WithTooltip>
/>
),
};
} else {
Expand Down Expand Up @@ -200,6 +165,7 @@ function viz(slice, payload) {
});
return row;
});

ReactDOM.render(
<Table
className="table table-no-hover"
Expand Down

0 comments on commit de73024

Please sign in to comment.