@superset-ui/legacy-plugin-chart-time-table
Version:
Superset Chart Plugin - Time Table
295 lines (251 loc) • 9.63 kB
JavaScript
;
exports.__esModule = true;
exports.default = void 0;
var _propTypes = _interopRequireDefault(require("prop-types"));
var _react = _interopRequireDefault(require("react"));
var _mustache = _interopRequireDefault(require("mustache"));
var _d3Scale = require("d3-scale");
var _reactableArc = require("reactable-arc");
var _core = require("@superset-ui/core");
var _chartControls = require("@superset-ui/chart-controls");
var _moment = _interopRequireDefault(require("moment"));
var _FormattedNumber = _interopRequireDefault(require("./FormattedNumber"));
var _SparklineCell = _interopRequireDefault(require("./SparklineCell"));
var _react2 = require("@emotion/react");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0'];
function colorFromBounds(value, bounds, colorBounds = ACCESSIBLE_COLOR_BOUNDS) {
if (bounds) {
const [min, max] = bounds;
const [minColor, maxColor] = colorBounds;
if (min !== null && max !== null) {
const colorScale = (0, _d3Scale.scaleLinear)().domain([min, (max + min) / 2, max]) // @ts-ignore
.range([minColor, 'grey', maxColor]); // @ts-ignore
return colorScale(value);
}
if (min !== null) {
// @ts-ignore
return value >= min ? maxColor : minColor;
}
if (max !== null) {
// @ts-ignore
return value < max ? maxColor : minColor;
}
}
return null;
}
class TimeTable extends _react.default.PureComponent {
renderLeftCell(row) {
const {
rowType,
url
} = this.props;
const context = {
metric: row
};
const fullUrl = url ? _mustache.default.render(url, context) : null;
if (rowType === 'column') {
const column = row;
if (fullUrl) {
return (0, _react2.jsx)("a", {
href: fullUrl,
rel: "noopener noreferrer",
target: "_blank"
}, column.label);
}
return column.label;
}
return (0, _react2.jsx)(_chartControls.MetricOption, {
openInNewWindow: true,
metric: row,
url: fullUrl,
showFormula: false
});
} // eslint-disable-next-line class-methods-use-this
renderSparklineCell(valueField, column, entries) {
let sparkData;
if (column.timeRatio) {
// Period ratio sparkline
sparkData = [];
for (let i = column.timeRatio; i < entries.length; i += 1) {
const prevData = entries[i - column.timeRatio][valueField];
if (prevData && prevData !== 0) {
sparkData.push(entries[i][valueField] / prevData);
} else {
// @ts-ignore
sparkData.push(null);
}
}
} else {
sparkData = entries.map(d => d[valueField]);
}
return (0, _react2.jsx)(_reactableArc.Td, {
key: column.key,
column: column.key,
value: sparkData[sparkData.length - 1]
}, (0, _react2.jsx)(_SparklineCell.default, {
width: parseInt(column.width, 10) || 300,
height: parseInt(column.height, 10) || 50,
data: sparkData,
ariaLabel: `spark-${valueField}`,
numberFormat: column.d3format,
yAxisBounds: column.yAxisBounds,
showYAxis: column.showYAxis,
renderTooltip: ({
index
}) => (0, _react2.jsx)("div", null, (0, _react2.jsx)("strong", null, (0, _core.formatNumber)(column.d3format, sparkData[index])), (0, _react2.jsx)("div", null, (0, _core.formatTime)(column.dateFormat, _moment.default.utc(entries[index].time).toDate())))
}));
} // eslint-disable-next-line class-methods-use-this
renderValueCell(valueField, column, reversedEntries) {
const recent = reversedEntries[0][valueField];
let v = 0;
let errorMsg;
if (column.colType === 'time') {
// Time lag ratio
const timeLag = column.timeLag || 0;
const totalLag = Object.keys(reversedEntries).length;
if (timeLag >= totalLag) {
errorMsg = `The time lag set at ${timeLag} is too large for the length of data at ${reversedEntries.length}. No data available.`;
} else {
v = reversedEntries[timeLag][valueField];
}
if (column.comparisonType === 'diff') {
v = recent - v;
} else if (column.comparisonType === 'perc') {
v = recent / v;
} else if (column.comparisonType === 'perc_change') {
v = recent / v - 1;
}
v = v || 0;
} else if (column.colType === 'contrib') {
// contribution to column total
v = recent / Object.keys(reversedEntries[0]).map(k => k === 'time' ? 0 : reversedEntries[0][k]).reduce((a, b) => a + b);
} else if (column.colType === 'avg') {
// Average over the last {timeLag}
v = reversedEntries.map((k, i) => i < column.timeLag ? k[valueField] : 0).reduce((a, b) => a + b) / column.timeLag;
}
const color = colorFromBounds(v, column.bounds);
return (0, _react2.jsx)(_reactableArc.Td, {
key: column.key,
column: column.key,
value: v,
style: color && {
boxShadow: `inset 0px -2.5px 0px 0px ${color}`,
borderRight: '2px solid #fff'
}
}, errorMsg ? (0, _react2.jsx)("div", null, errorMsg) : // @ts-ignore
(0, _react2.jsx)("div", {
style: {
color
}
}, (0, _react2.jsx)(_FormattedNumber.default, {
num: v,
format: column.d3format
})));
}
renderRow(row, entries, reversedEntries) {
const {
columnConfigs
} = this.props;
const valueField = row.label || row.metric_name;
const leftCell = this.renderLeftCell(row);
return (0, _react2.jsx)(_reactableArc.Tr, {
key: leftCell
}, (0, _react2.jsx)(_reactableArc.Td, {
column: "metric",
data: leftCell
}, leftCell), columnConfigs.map(c => c.colType === 'spark' ? this.renderSparklineCell(valueField, c, entries) : this.renderValueCell(valueField, c, reversedEntries)));
}
render() {
const {
className,
height,
data,
columnConfigs,
rowType,
rows
} = this.props;
const entries = Object.keys(data).sort() // @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
.map(time => ({ ...data[time],
time
}));
const reversedEntries = entries.concat().reverse();
const defaultSort = rowType === 'column' && columnConfigs.length > 0 ? {
column: columnConfigs[0].key,
direction: 'desc'
} : false;
return (0, _react2.jsx)("div", {
className: `time-table ${className}`,
style: {
height
}
}, (0, _react2.jsx)(_reactableArc.Table, {
className: "table table-no-hover",
defaultSort: defaultSort,
sortBy: defaultSort,
sortable: columnConfigs.map(c => c.key)
}, (0, _react2.jsx)(_reactableArc.Thead, null, (0, _react2.jsx)(_reactableArc.Th, {
column: "metric"
}, "Metric"), columnConfigs.map((c, i) => (0, _react2.jsx)(_reactableArc.Th, {
key: c.key,
column: c.key,
width: c.colType === 'spark' ? '1%' : null
}, c == null ? void 0 : c.label, ' ', (c == null ? void 0 : c.tooltip) && (0, _react2.jsx)(_chartControls.InfoTooltipWithTrigger, {
tooltip: c == null ? void 0 : c.tooltip,
label: `tt-col-${i}`,
placement: "top"
})))), rows.map(row => this.renderRow(row, entries, reversedEntries))));
}
}
TimeTable.propTypes = {
columnConfigs: _propTypes.default.arrayOf(_propTypes.default.shape({
colType: _propTypes.default.string.isRequired,
comparisonType: _propTypes.default.string.isRequired,
d3format: _propTypes.default.string.isRequired,
key: _propTypes.default.string.isRequired,
label: _propTypes.default.string.isRequired,
timeLag: _propTypes.default.number.isRequired,
tooltip: _propTypes.default.any.isRequired,
bounds: _propTypes.default.arrayOf(_propTypes.default.number).isRequired,
dateFormat: _propTypes.default.string.isRequired,
width: _propTypes.default.string.isRequired,
height: _propTypes.default.string.isRequired,
yAxisBounds: _propTypes.default.arrayOf(_propTypes.default.number).isRequired,
showYAxis: _propTypes.default.bool.isRequired,
timeRatio: _propTypes.default.number.isRequired
})).isRequired,
data: _propTypes.default.object.isRequired,
height: _propTypes.default.number.isRequired,
rows: _propTypes.default.arrayOf(_propTypes.default.shape({
label: _propTypes.default.string.isRequired,
metric_name: _propTypes.default.string.isRequired
})).isRequired,
rowType: _propTypes.default.string.isRequired,
url: _propTypes.default.string.isRequired,
row: _propTypes.default.arrayOf(_propTypes.default.any).isRequired
};
var _default = (0, _core.styled)(TimeTable)`
.time-table {
overflow: auto;
}
`;
exports.default = _default;