mirror of
https://github.com/apache/superset.git
synced 2026-04-27 12:05:24 +00:00
214 lines
6.9 KiB
JavaScript
214 lines
6.9 KiB
JavaScript
import ReactDOM from 'react-dom';
|
|
import React from 'react';
|
|
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 '../javascripts/components/MetricOption';
|
|
import { d3format, brandColor } from '../javascripts/modules/utils';
|
|
import { formatDate } from '../javascripts/modules/dates';
|
|
import InfoTooltipWithTrigger from '../javascripts/components/InfoTooltipWithTrigger';
|
|
import './time_table.css';
|
|
|
|
const SPARKLINE_MARGIN = {
|
|
top: 8,
|
|
right: 8,
|
|
bottom: 8,
|
|
left: 8,
|
|
};
|
|
const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];
|
|
|
|
function FormattedNumber({ num, format }) {
|
|
if (format) {
|
|
return (
|
|
<span title={num}>{d3format(format, num)}</span>
|
|
);
|
|
}
|
|
return <span>{num}</span>;
|
|
}
|
|
|
|
FormattedNumber.propTypes = {
|
|
num: propTypes.number,
|
|
format: propTypes.string,
|
|
};
|
|
|
|
function viz(slice, payload) {
|
|
slice.container.css('height', slice.height());
|
|
const records = payload.data.records;
|
|
const fd = payload.form_data;
|
|
const data = Object.keys(records).sort().map(iso => ({ ...records[iso], iso }));
|
|
const reversedData = [...data].reverse();
|
|
const metricMap = {};
|
|
slice.datasource.metrics.forEach((m) => {
|
|
metricMap[m.metric_name] = m;
|
|
});
|
|
|
|
let metrics;
|
|
let defaultSort = false;
|
|
if (payload.data.is_group_by) {
|
|
metrics = payload.data.columns;
|
|
defaultSort = { column: fd.column_collection[0].key, direction: 'desc' };
|
|
} else {
|
|
metrics = fd.metrics;
|
|
}
|
|
const tableData = metrics.map((metric) => {
|
|
let leftCell;
|
|
const context = { ...fd, metric };
|
|
const url = fd.url ? Mustache.render(fd.url, context) : null;
|
|
if (!payload.data.is_group_by) {
|
|
leftCell = (
|
|
<MetricOption metric={metricMap[metric]} url={url} showFormula={false} openInNewWindow />
|
|
);
|
|
} else {
|
|
leftCell = url ? <a href={url} target="_blank">{metric}</a> : metric;
|
|
}
|
|
const row = { metric: leftCell };
|
|
fd.column_collection.forEach((c) => {
|
|
if (c.colType === 'spark') {
|
|
let sparkData;
|
|
if (!c.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];
|
|
if (prevData && prevData !== 0) {
|
|
sparkData.push(data[i][metric] / prevData);
|
|
} else {
|
|
sparkData.push(null);
|
|
}
|
|
}
|
|
}
|
|
row[c.key] = {
|
|
data: sparkData[sparkData.length - 1],
|
|
display: (
|
|
<WithTooltip
|
|
renderTooltip={({ index }) => (
|
|
<div>
|
|
<strong>{d3format(c.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}
|
|
margin={SPARKLINE_MARGIN}
|
|
data={sparkData}
|
|
onMouseLeave={onMouseLeave}
|
|
onMouseMove={onMouseMove}
|
|
>
|
|
<LineSeries
|
|
showArea={false}
|
|
stroke={brandColor}
|
|
/>
|
|
{tooltipData &&
|
|
<VerticalReferenceLine
|
|
reference={tooltipData.index}
|
|
strokeDasharray="3 3"
|
|
strokeWidth={1}
|
|
/>}
|
|
{tooltipData &&
|
|
<PointSeries
|
|
points={[tooltipData.index]}
|
|
fill={brandColor}
|
|
strokeWidth={1}
|
|
/>}
|
|
</Sparkline>
|
|
)}
|
|
</WithTooltip>
|
|
),
|
|
};
|
|
} else {
|
|
const recent = reversedData[0][metric];
|
|
let v;
|
|
if (c.colType === 'time') {
|
|
// Time lag ratio
|
|
v = reversedData[parseInt(c.timeLag, 10)][metric];
|
|
if (c.comparisonType === 'diff') {
|
|
v = recent - v;
|
|
} else if (c.comparisonType === 'perc') {
|
|
v = recent / v;
|
|
} else if (c.comparisonType === 'perc_change') {
|
|
v = (recent / v) - 1;
|
|
}
|
|
} else if (c.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') {
|
|
// Average over the last {timeLag}
|
|
v = reversedData
|
|
.map((k, i) => i < c.timeLag ? k[metric] : 0)
|
|
.reduce((a, b) => a + b) / c.timeLag;
|
|
}
|
|
let color;
|
|
if (c.bounds && c.bounds[0] !== null && c.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]])
|
|
.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];
|
|
}
|
|
row[c.key] = {
|
|
data: v,
|
|
display: (
|
|
<span style={{ color }}>
|
|
<FormattedNumber num={v} format={c.d3format} />
|
|
</span>),
|
|
};
|
|
}
|
|
});
|
|
return row;
|
|
});
|
|
ReactDOM.render(
|
|
<Table
|
|
className="table table-condensed"
|
|
defaultSort={defaultSort}
|
|
sortBy={defaultSort}
|
|
sortable={fd.column_collection.map(c => c.key)}
|
|
>
|
|
<Thead>
|
|
<Th column="metric">Metric</Th>
|
|
{fd.column_collection.map((c, i) => (
|
|
<Th column={c.key} key={c.key} width={c.colType === 'spark' ? '1%' : null}>
|
|
{c.label} {c.tooltip && (
|
|
<InfoTooltipWithTrigger
|
|
tooltip={c.tooltip}
|
|
label={`tt-col-${i}`}
|
|
placement="top"
|
|
/>
|
|
)}
|
|
</Th>))}
|
|
</Thead>
|
|
{tableData.map(row => (
|
|
<Tr key={row.metric}>
|
|
<Td column="metric" data={row.metric}>{row.metric}</Td>
|
|
{fd.column_collection.map(c => (
|
|
<Td
|
|
column={c.key}
|
|
key={c.key}
|
|
value={row[c.key].data}
|
|
>
|
|
{row[c.key].display}
|
|
</Td>))}
|
|
</Tr>))}
|
|
</Table>,
|
|
document.getElementById(slice.containerId),
|
|
);
|
|
}
|
|
|
|
module.exports = viz;
|