mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
[time table] add tooltip to sparkline (#3767)
* [time table] add tooltip to sparkline * [time table] open link in new tab * [time table] add back Mustache
This commit is contained in:
parent
7fd9c82ae8
commit
7f3edad119
@ -5,6 +5,7 @@ import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
metric: PropTypes.object.isRequired,
|
metric: PropTypes.object.isRequired,
|
||||||
|
openInNewWindow: PropTypes.bool,
|
||||||
showFormula: PropTypes.bool,
|
showFormula: PropTypes.bool,
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
};
|
};
|
||||||
@ -12,9 +13,9 @@ const defaultProps = {
|
|||||||
showFormula: true,
|
showFormula: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MetricOption({ metric, showFormula, url }) {
|
export default function MetricOption({ metric, openInNewWindow, showFormula, url }) {
|
||||||
const verbose = metric.verbose_name || metric.metric_name;
|
const verbose = metric.verbose_name || metric.metric_name;
|
||||||
const link = url ? <a href={url}>{verbose}</a> : verbose;
|
const link = url ? <a href={url} target={openInNewWindow ? '_blank' : null}>{verbose}</a> : verbose;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="m-r-5 option-label">{link}</span>
|
<span className="m-r-5 option-label">{link}</span>
|
||||||
|
@ -64,7 +64,6 @@ export const tickMultiFormat = d3.time.format.multi([
|
|||||||
]);
|
]);
|
||||||
export const formatDate = function (dttm) {
|
export const formatDate = function (dttm) {
|
||||||
const d = UTC(new Date(dttm));
|
const d = UTC(new Date(dttm));
|
||||||
// d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
|
|
||||||
return tickMultiFormat(d);
|
return tickMultiFormat(d);
|
||||||
};
|
};
|
||||||
export const fDuration = function (t1, t2, f = 'HH:mm:ss.SS') {
|
export const fDuration = function (t1, t2, f = 'HH:mm:ss.SS') {
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
"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.1",
|
"@data-ui/sparkline": "0.0.47",
|
||||||
"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",
|
||||||
|
@ -50,4 +50,13 @@ describe('MetricOption', () => {
|
|||||||
wrapper = shallow(factory(props));
|
wrapper = shallow(factory(props));
|
||||||
expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(1);
|
expect(wrapper.find(InfoTooltipWithTrigger)).to.have.length(1);
|
||||||
});
|
});
|
||||||
|
it('sets target="_blank" when openInNewWindow is true', () => {
|
||||||
|
props.url = 'https://github.com/apache/incubator-superset';
|
||||||
|
wrapper = shallow(factory(props));
|
||||||
|
expect(wrapper.find('a').prop('target')).to.equal(null);
|
||||||
|
|
||||||
|
props.openInNewWindow = true;
|
||||||
|
wrapper = shallow(factory(props));
|
||||||
|
expect(wrapper.find('a').prop('target')).to.equal('_blank');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,16 +3,21 @@ import React from 'react';
|
|||||||
import propTypes from 'prop-types';
|
import propTypes from 'prop-types';
|
||||||
import { Table, Thead, Th, Tr, Td } from 'reactable';
|
import { Table, Thead, Th, Tr, Td } from 'reactable';
|
||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
import { Sparkline, LineSeries } from '@data-ui/sparkline';
|
|
||||||
import Mustache from 'mustache';
|
import Mustache from 'mustache';
|
||||||
|
import { Sparkline, LineSeries, PointSeries, VerticalReferenceLine, WithTooltip } from '@data-ui/sparkline';
|
||||||
|
|
||||||
import MetricOption from '../javascripts/components/MetricOption';
|
import MetricOption from '../javascripts/components/MetricOption';
|
||||||
import TooltipWrapper from '../javascripts/components/TooltipWrapper';
|
|
||||||
import { d3format, brandColor } from '../javascripts/modules/utils';
|
import { d3format, brandColor } from '../javascripts/modules/utils';
|
||||||
|
import { formatDate } from '../javascripts/modules/dates';
|
||||||
import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger';
|
import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger';
|
||||||
import './time_table.css';
|
import './time_table.css';
|
||||||
|
|
||||||
const SPARK_MARGIN = 3;
|
const SPARKLINE_MARGIN = {
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
bottom: 8,
|
||||||
|
left: 8,
|
||||||
|
};
|
||||||
const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];
|
const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];
|
||||||
|
|
||||||
function FormattedNumber({ num, format }) {
|
function FormattedNumber({ num, format }) {
|
||||||
@ -23,6 +28,7 @@ function FormattedNumber({ num, format }) {
|
|||||||
}
|
}
|
||||||
return <span>{num}</span>;
|
return <span>{num}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormattedNumber.propTypes = {
|
FormattedNumber.propTypes = {
|
||||||
num: propTypes.number,
|
num: propTypes.number,
|
||||||
format: propTypes.string,
|
format: propTypes.string,
|
||||||
@ -30,14 +36,10 @@ FormattedNumber.propTypes = {
|
|||||||
|
|
||||||
function viz(slice, payload) {
|
function viz(slice, payload) {
|
||||||
slice.container.css('height', slice.height());
|
slice.container.css('height', slice.height());
|
||||||
const recs = payload.data.records;
|
const records = payload.data.records;
|
||||||
const fd = payload.form_data;
|
const fd = payload.form_data;
|
||||||
const data = Object.keys(recs).sort().map((iso) => {
|
const data = Object.keys(records).sort().map(iso => ({ ...records[iso], iso }));
|
||||||
const o = recs[iso];
|
const reversedData = [...data].reverse();
|
||||||
return o;
|
|
||||||
});
|
|
||||||
const reversedData = data.slice();
|
|
||||||
reversedData.reverse();
|
|
||||||
const metricMap = {};
|
const metricMap = {};
|
||||||
slice.datasource.metrics.forEach((m) => {
|
slice.datasource.metrics.forEach((m) => {
|
||||||
metricMap[m.metric_name] = m;
|
metricMap[m.metric_name] = m;
|
||||||
@ -53,13 +55,14 @@ function viz(slice, payload) {
|
|||||||
}
|
}
|
||||||
const tableData = metrics.map((metric) => {
|
const tableData = metrics.map((metric) => {
|
||||||
let leftCell;
|
let leftCell;
|
||||||
const context = Object.assign({}, fd, { metric });
|
const context = { ...fd, metric };
|
||||||
const url = fd.url ? Mustache.render(fd.url, context) : null;
|
const url = fd.url ? Mustache.render(fd.url, context) : null;
|
||||||
|
|
||||||
if (!payload.data.is_group_by) {
|
if (!payload.data.is_group_by) {
|
||||||
leftCell = <MetricOption metric={metricMap[metric]} url={url}showFormula={false} />;
|
leftCell = (
|
||||||
|
<MetricOption metric={metricMap[metric]} url={url} showFormula={false} openInNewWindow />
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
leftCell = url ? <a href={url}>{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((c) => {
|
||||||
@ -79,33 +82,47 @@ function viz(slice, payload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const extent = d3.extent(sparkData);
|
|
||||||
const tooltip = `min: ${d3format(c.d3format, extent[0])}, \
|
|
||||||
max: ${d3format(c.d3format, extent[1])}`;
|
|
||||||
row[c.key] = {
|
row[c.key] = {
|
||||||
data: sparkData[sparkData.length - 1],
|
data: sparkData[sparkData.length - 1],
|
||||||
display: (
|
display: (
|
||||||
<TooltipWrapper label="tt-spark" tooltip={tooltip}>
|
<WithTooltip
|
||||||
<div>
|
renderTooltip={({ index }) => (
|
||||||
|
<div style={{ minWidth: 140 }}>
|
||||||
|
<strong>{d3format(c.d3format, data[index][metric])}</strong>
|
||||||
|
<div>{formatDate(data[index].iso)}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({ onMouseLeave, onMouseMove, tooltipData }) => (
|
||||||
<Sparkline
|
<Sparkline
|
||||||
ariaLabel={`spark-${metric}`}
|
ariaLabel={`spark-${metric}`}
|
||||||
width={parseInt(c.width, 10) || 300}
|
width={parseInt(c.width, 10) || 300}
|
||||||
height={parseInt(c.height, 10) || 50}
|
height={parseInt(c.height, 10) || 50}
|
||||||
margin={{
|
margin={SPARKLINE_MARGIN}
|
||||||
top: SPARK_MARGIN,
|
|
||||||
bottom: SPARK_MARGIN,
|
|
||||||
left: SPARK_MARGIN,
|
|
||||||
right: SPARK_MARGIN,
|
|
||||||
}}
|
|
||||||
data={sparkData}
|
data={sparkData}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onMouseMove={onMouseMove}
|
||||||
>
|
>
|
||||||
<LineSeries
|
<LineSeries
|
||||||
showArea={false}
|
showArea={false}
|
||||||
stroke={brandColor}
|
stroke={brandColor}
|
||||||
/>
|
/>
|
||||||
|
{tooltipData &&
|
||||||
|
<VerticalReferenceLine
|
||||||
|
reference={tooltipData.index}
|
||||||
|
strokeDasharray="3 3"
|
||||||
|
strokeWidth={1}
|
||||||
|
/>}
|
||||||
|
{tooltipData &&
|
||||||
|
<PointSeries
|
||||||
|
points={[tooltipData.index]}
|
||||||
|
fill={brandColor}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>}
|
||||||
</Sparkline>
|
</Sparkline>
|
||||||
</div>
|
)}
|
||||||
</TooltipWrapper>),
|
</WithTooltip>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const recent = reversedData[0][metric];
|
const recent = reversedData[0][metric];
|
||||||
|
@ -47,7 +47,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.jsx?$/,
|
||||||
exclude: APP_DIR + '/node_modules',
|
exclude: /node_modules/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
query: {
|
query: {
|
||||||
presets: [
|
presets: [
|
||||||
|
Loading…
Reference in New Issue
Block a user