mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
feat(big-number): allow fallback to last available value and fix time range for trend lines (#403)
* feat(big-number): add option to align time range In Superset, when a timeseries query has no data at the beginning period or end period of the filtered time range, there will not no data records at those periods, hence the trendline in Big Number chart would not render those periods. This often causes confusion and misinterpretaiton in dashboards, especially for those with multiple trendline charts aligned with each other. They could all be a very smooth line, but actually showing very different time ranges. This PR adds an option "alignTimeRange" to apply the filtered time range on the xAxis. Date periods for empty data will be rendered, but there will be no connected lines, dots, or tooltips for them. It's possible to still show tooltips for those periods, but I decided not to do that as: 1) it makes things much more complicated; 2) I don't want to confuse zero or nulls with empty data. * fix(big-number): disable alignRange by default * refactor(big-number): migrate to Typescript * fix(big-number): typescript build * fix(big-number): change tooltip trigger; fix storybook * fix(big-number): move @types to dependencies * fix(big-number): move all files to ts * build(big-number): add @types/d3-color as dependency * refactor(big-number): remove renderTooltip as prop * feat(big-number): add timeRangeUseFallback options and some refactor * fix(big-number): update formatting functions * fix(big-number): update copy for no data * fix(big-number): address PR feedbacks * feat(big-number): replace timeRangeUseFallback with bigNumberFallback * fix: upgrade @types/react-bootstrap * build(big-number): move react-bootstrap to dependencies * refactor(big-number): more coherent types * feat(big-number): use alert box for fallback values * build(big-number): remove react-bootstrap * build: upgrade nimbus and fix versions Keep running into building errors locally, so upgrade nimbus and fix all related packages to the working latest version. * feat(big-number): adjust fallback warning alignment * build: use a non-fixed version for @types/shortid * build: revert package versions in main
This commit is contained in:
parent
cb3206b583
commit
80b6e066eb
@ -29,8 +29,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@data-ui/xy-chart": "^0.0.84",
|
||||
"@types/d3-color": "^1.2.2",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"d3-color": "^1.2.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"shortid": "^2.2.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -20,7 +20,6 @@
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
Open Sans, Helvetica Neue, sans-serif;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@ -31,10 +30,18 @@
|
||||
}
|
||||
|
||||
.superset-legacy-chart-big-number .text-container {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.superset-legacy-chart-big-number .text-container .alert {
|
||||
font-size: 11px;
|
||||
margin: -0.5em 0 0.4em;
|
||||
line-height: 1;
|
||||
padding: 2px 4px 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.superset-legacy-chart-big-number .header-line {
|
||||
@ -46,8 +53,6 @@
|
||||
.superset-legacy-chart-big-number .header-line span {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.superset-legacy-chart-big-number .subheader-line {
|
||||
@ -55,3 +60,13 @@
|
||||
padding-bottom: 0;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.superset-legacy-chart-big-number.is-fallback-value .header-line,
|
||||
.superset-legacy-chart-big-number.is-fallback-value .subheader-line {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.superset-data-ui-tooltip {
|
||||
z-index: 1000;
|
||||
background: #000;
|
||||
}
|
||||
|
@ -1,259 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
/* eslint-disable react/jsx-sort-default-props */
|
||||
/* eslint-disable react/sort-prop-types */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import shortid from 'shortid';
|
||||
import { XYChart, AreaSeries, CrossHair, LinearGradient } from '@data-ui/xy-chart';
|
||||
import { BRAND_COLOR } from '@superset-ui/color';
|
||||
import { computeMaxFontSize } from '@superset-ui/dimension';
|
||||
|
||||
import './BigNumber.css';
|
||||
|
||||
const CHART_MARGIN = {
|
||||
top: 4,
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
};
|
||||
|
||||
const PROPORTION = {
|
||||
HEADER: 0.3,
|
||||
SUBHEADER: 0.125,
|
||||
TRENDLINE: 0.3,
|
||||
};
|
||||
|
||||
export function renderTooltipFactory(formatDate, formatValue) {
|
||||
function renderTooltip({ datum }) {
|
||||
const { x: rawDate, y: rawValue } = datum;
|
||||
const formattedDate = formatDate(rawDate);
|
||||
const value = formatValue(rawValue);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '4px 8px' }}>
|
||||
{formattedDate}
|
||||
<br />
|
||||
<strong>{value}</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTooltip.propTypes = {
|
||||
datum: PropTypes.shape({
|
||||
x: PropTypes.instanceOf(Date),
|
||||
y: PropTypes.number,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
return renderTooltip;
|
||||
}
|
||||
|
||||
function identity(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
bigNumber: PropTypes.number.isRequired,
|
||||
formatBigNumber: PropTypes.func,
|
||||
headerFontSize: PropTypes.number,
|
||||
subheader: PropTypes.string,
|
||||
subheaderFontSize: PropTypes.number,
|
||||
showTrendLine: PropTypes.bool,
|
||||
startYAxisAtZero: PropTypes.bool,
|
||||
trendLineData: PropTypes.array,
|
||||
mainColor: PropTypes.string,
|
||||
renderTooltip: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
formatBigNumber: identity,
|
||||
headerFontSize: PROPORTION.HEADER,
|
||||
subheader: '',
|
||||
subheaderFontSize: PROPORTION.SUBHEADER,
|
||||
showTrendLine: false,
|
||||
startYAxisAtZero: true,
|
||||
trendLineData: null,
|
||||
mainColor: BRAND_COLOR,
|
||||
renderTooltip: renderTooltipFactory(identity, identity),
|
||||
};
|
||||
|
||||
class BigNumberVis extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.gradientId = shortid.generate();
|
||||
}
|
||||
|
||||
getClassName() {
|
||||
const { className, showTrendLine } = this.props;
|
||||
const names = `superset-legacy-chart-big-number ${className}`;
|
||||
if (showTrendLine) {
|
||||
return names;
|
||||
}
|
||||
|
||||
return `${names} no-trendline`;
|
||||
}
|
||||
|
||||
createTemporaryContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.className = this.getClassName();
|
||||
container.style.position = 'absolute'; // so it won't disrupt page layout
|
||||
container.style.opacity = 0; // and not visible
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
renderHeader(maxHeight) {
|
||||
const { bigNumber, formatBigNumber, width } = this.props;
|
||||
const text = bigNumber === null ? 'No data' : formatBigNumber(bigNumber);
|
||||
|
||||
const container = this.createTemporaryContainer();
|
||||
document.body.append(container);
|
||||
const fontSize = computeMaxFontSize({
|
||||
text,
|
||||
maxWidth: Math.floor(width),
|
||||
maxHeight,
|
||||
className: 'header-line',
|
||||
container,
|
||||
});
|
||||
document.body.removeChild(container);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="header-line"
|
||||
style={{
|
||||
fontSize,
|
||||
height: maxHeight,
|
||||
}}
|
||||
>
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSubheader(maxHeight) {
|
||||
const { bigNumber, subheader, width } = this.props;
|
||||
let fontSize = 0;
|
||||
|
||||
const text =
|
||||
bigNumber === null
|
||||
? 'Try applying different filters or ensuring your Datasource contains data'
|
||||
: subheader;
|
||||
if (text) {
|
||||
const container = this.createTemporaryContainer();
|
||||
document.body.append(container);
|
||||
fontSize = computeMaxFontSize({
|
||||
text,
|
||||
maxWidth: Math.floor(width),
|
||||
maxHeight,
|
||||
className: 'subheader-line',
|
||||
container,
|
||||
});
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="subheader-line"
|
||||
style={{
|
||||
fontSize,
|
||||
height: maxHeight,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTrendline(maxHeight) {
|
||||
const {
|
||||
width,
|
||||
trendLineData,
|
||||
mainColor,
|
||||
subheader,
|
||||
renderTooltip,
|
||||
startYAxisAtZero,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<XYChart
|
||||
snapTooltipToDataX
|
||||
ariaLabel={`Big number visualization ${subheader}`}
|
||||
xScale={{ type: 'timeUtc' }}
|
||||
yScale={{
|
||||
type: 'linear',
|
||||
includeZero: startYAxisAtZero,
|
||||
}}
|
||||
width={Math.floor(width)}
|
||||
height={maxHeight}
|
||||
margin={CHART_MARGIN}
|
||||
renderTooltip={renderTooltip}
|
||||
>
|
||||
<LinearGradient id={this.gradientId} from={mainColor} to="#fff" />
|
||||
<AreaSeries data={trendLineData} fill={`url(#${this.gradientId})`} stroke={mainColor} />
|
||||
<CrossHair
|
||||
fullHeight
|
||||
stroke={mainColor}
|
||||
circleFill={mainColor}
|
||||
circleStroke="#fff"
|
||||
showHorizontalLine={false}
|
||||
strokeDasharray="5,2"
|
||||
/>
|
||||
</XYChart>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showTrendLine, height, headerFontSize, subheaderFontSize } = this.props;
|
||||
const className = this.getClassName();
|
||||
|
||||
if (showTrendLine) {
|
||||
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
|
||||
const allTextHeight = height - chartHeight;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="text-container" style={{ height: allTextHeight }}>
|
||||
{this.renderHeader(Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height))}
|
||||
{this.renderSubheader(
|
||||
Math.ceil(subheaderFontSize * (1 - PROPORTION.TRENDLINE) * height),
|
||||
)}
|
||||
</div>
|
||||
{this.renderTrendline(chartHeight)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} style={{ height }}>
|
||||
{this.renderHeader(Math.ceil(headerFontSize * height))}
|
||||
{this.renderSubheader(Math.ceil(subheaderFontSize * height))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BigNumberVis.propTypes = propTypes;
|
||||
BigNumberVis.defaultProps = defaultProps;
|
||||
|
||||
export default BigNumberVis;
|
@ -0,0 +1,305 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import shortid from 'shortid';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { getNumberFormatter } from '@superset-ui/number-format';
|
||||
import { XYChart, AreaSeries, CrossHair, LinearGradient } from '@data-ui/xy-chart';
|
||||
import { BRAND_COLOR } from '@superset-ui/color';
|
||||
import { computeMaxFontSize } from '@superset-ui/dimension';
|
||||
import NumberFormatter from '@superset-ui/number-format/lib/NumberFormatter';
|
||||
import { smartDateVerboseFormatter } from '@superset-ui/time-format';
|
||||
import TimeFormatter from '@superset-ui/time-format/lib/TimeFormatter';
|
||||
|
||||
import './BigNumber.css';
|
||||
|
||||
const defaultNumberFormatter = getNumberFormatter();
|
||||
|
||||
const CHART_MARGIN = {
|
||||
top: 4,
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
};
|
||||
|
||||
const PROPORTION = {
|
||||
// text size: proportion of the chart container sans trendline
|
||||
HEADER: 0.3,
|
||||
SUBHEADER: 0.125,
|
||||
// trendline size: proportion of the whole chart container
|
||||
TRENDLINE: 0.3,
|
||||
};
|
||||
|
||||
type TimeSeriesDatum = {
|
||||
x: number; // timestamp as a number
|
||||
y: number | null;
|
||||
};
|
||||
|
||||
export function renderTooltipFactory(
|
||||
formatDate = smartDateVerboseFormatter,
|
||||
formatValue = defaultNumberFormatter,
|
||||
) {
|
||||
return function renderTooltip({ datum: { x, y } }: { datum: TimeSeriesDatum }) {
|
||||
// even though `formatDate` supports timestamp as numbers, we need
|
||||
// `new Date` to pass type check
|
||||
return (
|
||||
<div style={{ padding: '4px 8px' }}>
|
||||
{formatDate(new Date(x))}
|
||||
<br />
|
||||
<strong>{y === null ? t('N/A') : formatValue(y)}</strong>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
type BigNumberVisProps = {
|
||||
className?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
bigNumber?: number | null;
|
||||
bigNumberFallback?: TimeSeriesDatum;
|
||||
formatNumber: NumberFormatter;
|
||||
formatTime: TimeFormatter;
|
||||
fromDatetime?: number;
|
||||
toDatetime?: number;
|
||||
headerFontSize: number;
|
||||
subheader: string;
|
||||
subheaderFontSize: number;
|
||||
showTrendLine?: boolean;
|
||||
startYAxisAtZero?: boolean;
|
||||
timeRangeFixed?: boolean;
|
||||
trendLineData?: TimeSeriesDatum[];
|
||||
mainColor: string;
|
||||
};
|
||||
|
||||
class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
||||
private gradientId: string = shortid.generate();
|
||||
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
formatNumber: (num: number) => String(num),
|
||||
formatTime: smartDateVerboseFormatter.formatFunc,
|
||||
headerFontSize: PROPORTION.HEADER,
|
||||
mainColor: BRAND_COLOR,
|
||||
showTrendLine: false,
|
||||
startYAxisAtZero: true,
|
||||
subheader: '',
|
||||
subheaderFontSize: PROPORTION.SUBHEADER,
|
||||
timeRangeFixed: false,
|
||||
};
|
||||
|
||||
getClassName() {
|
||||
const { className, showTrendLine, bigNumberFallback } = this.props;
|
||||
const names = `superset-legacy-chart-big-number ${className} ${
|
||||
bigNumberFallback ? 'is-fallback-value' : ''
|
||||
}`;
|
||||
if (showTrendLine) return names;
|
||||
return `${names} no-trendline`;
|
||||
}
|
||||
|
||||
createTemporaryContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.className = this.getClassName();
|
||||
container.style.position = 'absolute'; // so it won't disrupt page layout
|
||||
container.style.opacity = '0'; // and not visible
|
||||
return container;
|
||||
}
|
||||
|
||||
renderFallbackWarning() {
|
||||
const { bigNumberFallback, formatTime } = this.props;
|
||||
if (!bigNumberFallback) return null;
|
||||
return (
|
||||
<span
|
||||
className="alert alert-warning"
|
||||
role="alert"
|
||||
title={t(`Last available value seen on %s`, formatTime(bigNumberFallback.x))}
|
||||
>
|
||||
{t('Not up to date')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderHeader(maxHeight: number) {
|
||||
const { bigNumber, formatNumber, width } = this.props;
|
||||
const text = bigNumber === null ? t('No data') : formatNumber(bigNumber);
|
||||
|
||||
const container = this.createTemporaryContainer();
|
||||
document.body.append(container);
|
||||
const fontSize = computeMaxFontSize({
|
||||
text,
|
||||
maxWidth: width,
|
||||
maxHeight,
|
||||
className: 'header-line',
|
||||
container,
|
||||
});
|
||||
document.body.removeChild(container);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="header-line"
|
||||
style={{
|
||||
fontSize,
|
||||
height: maxHeight,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSubheader(maxHeight: number) {
|
||||
const { bigNumber, subheader, width, bigNumberFallback } = this.props;
|
||||
let fontSize = 0;
|
||||
|
||||
const NO_DATA_OR_HASNT_LANDED = t(
|
||||
'No data after filtering or data is NULL for the latest time record',
|
||||
);
|
||||
const NO_DATA = t('Try applying different filters or ensuring your datasource has data');
|
||||
let text = subheader;
|
||||
if (bigNumber === null) {
|
||||
text = bigNumberFallback ? NO_DATA : NO_DATA_OR_HASNT_LANDED;
|
||||
}
|
||||
if (text) {
|
||||
const container = this.createTemporaryContainer();
|
||||
document.body.append(container);
|
||||
fontSize = computeMaxFontSize({
|
||||
text,
|
||||
maxWidth: width,
|
||||
maxHeight,
|
||||
className: 'subheader-line',
|
||||
container,
|
||||
});
|
||||
document.body.removeChild(container);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="subheader-line"
|
||||
style={{
|
||||
fontSize,
|
||||
height: maxHeight,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderTrendline(maxHeight: number) {
|
||||
const {
|
||||
width,
|
||||
trendLineData,
|
||||
mainColor,
|
||||
subheader,
|
||||
startYAxisAtZero,
|
||||
formatNumber,
|
||||
formatTime,
|
||||
fromDatetime,
|
||||
timeRangeFixed,
|
||||
} = this.props;
|
||||
|
||||
// if can't find any non-null values, no point rendering the trendline
|
||||
if (!trendLineData?.some(d => d.y !== null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Apply a fixed X range if a time range is specified.
|
||||
//
|
||||
// XYChart checks the existence of `domain` property and decide whether to
|
||||
// apply a domain or not, so it must not be `null` or `undefined`
|
||||
const xScale: { type: string; domain?: number[] } = { type: 'timeUtc' };
|
||||
const tooltipData = trendLineData && [...trendLineData];
|
||||
if (tooltipData && timeRangeFixed && fromDatetime) {
|
||||
const toDatetime = this.props.toDatetime ?? Date.now();
|
||||
if (tooltipData[0].x > fromDatetime) {
|
||||
tooltipData.unshift({
|
||||
x: fromDatetime,
|
||||
y: null,
|
||||
});
|
||||
}
|
||||
if (tooltipData[tooltipData.length - 1].x < toDatetime) {
|
||||
tooltipData.push({
|
||||
x: toDatetime,
|
||||
y: null,
|
||||
});
|
||||
}
|
||||
xScale.domain = [fromDatetime, toDatetime];
|
||||
}
|
||||
return (
|
||||
<XYChart
|
||||
snapTooltipToDataX
|
||||
ariaLabel={`Big number visualization ${subheader}`}
|
||||
renderTooltip={renderTooltipFactory(formatTime, formatNumber)}
|
||||
xScale={xScale}
|
||||
yScale={{
|
||||
type: 'linear',
|
||||
includeZero: startYAxisAtZero,
|
||||
}}
|
||||
width={Math.floor(width)}
|
||||
height={maxHeight}
|
||||
margin={CHART_MARGIN}
|
||||
eventTrigger="container"
|
||||
>
|
||||
<LinearGradient id={this.gradientId} from={mainColor} to="#fff" />
|
||||
<AreaSeries data={tooltipData} fill={`url(#${this.gradientId})`} stroke={mainColor} />
|
||||
<CrossHair
|
||||
fullHeight
|
||||
stroke={mainColor}
|
||||
circleFill={mainColor}
|
||||
circleStroke="#fff"
|
||||
showHorizontalLine={false}
|
||||
strokeDasharray="5,2"
|
||||
/>
|
||||
</XYChart>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showTrendLine, height, headerFontSize, subheaderFontSize } = this.props;
|
||||
const className = this.getClassName();
|
||||
|
||||
if (showTrendLine) {
|
||||
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
|
||||
const allTextHeight = height - chartHeight;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="text-container" style={{ height: allTextHeight }}>
|
||||
{this.renderFallbackWarning()}
|
||||
{this.renderHeader(Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height))}
|
||||
{this.renderSubheader(
|
||||
Math.ceil(subheaderFontSize * (1 - PROPORTION.TRENDLINE) * height),
|
||||
)}
|
||||
</div>
|
||||
{this.renderTrendline(chartHeight)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} style={{ height }}>
|
||||
{this.renderHeader(Math.ceil(headerFontSize * height))}
|
||||
{this.renderSubheader(Math.ceil(subheaderFontSize * height))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BigNumberVis;
|
@ -1,149 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import * as color from 'd3-color';
|
||||
import { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';
|
||||
import { getTimeFormatter, TimeFormats, smartDateVerboseFormatter } from '@superset-ui/time-format';
|
||||
import { renderTooltipFactory } from './BigNumber';
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
|
||||
function getTimeFormatterForGranularity(granularity) {
|
||||
// Translate time granularity to d3-format
|
||||
const MINUTE = '%Y-%m-%d %H:%M';
|
||||
const SUNDAY_BASED_WEEK = '%Y W%U';
|
||||
const MONDAY_BASED_WEEK = '%Y W%W';
|
||||
const { DATABASE_DATE, DATABASE_DATETIME } = TimeFormats;
|
||||
|
||||
// search for `builtin_time_grains` in incubator-superset/superset/db_engine_specs/base.py
|
||||
const formats = {
|
||||
date: DATABASE_DATE,
|
||||
PT1S: DATABASE_DATETIME, // second
|
||||
PT1M: MINUTE, // minute
|
||||
PT5M: MINUTE, // 5 minute
|
||||
PT10M: MINUTE, // 10 minute
|
||||
PT15M: MINUTE, // 15 minute
|
||||
'PT0.5H': MINUTE, // half hour
|
||||
PT1H: '%Y-%m-%d %H:00', // hour
|
||||
P1D: DATABASE_DATE, // day
|
||||
P1W: SUNDAY_BASED_WEEK, // week
|
||||
P1M: 'smart_date_verbose', // month
|
||||
'P0.25Y': '%Y Q%q', // quarter
|
||||
P1Y: '%Y', // year
|
||||
// d3-time-format weeks does not support weeks start on Sunday
|
||||
'1969-12-28T00:00:00Z/P1W': SUNDAY_BASED_WEEK, // 'week_start_sunday'
|
||||
'1969-12-29T00:00:00Z/P1W': MONDAY_BASED_WEEK, // 'week_start_monday'
|
||||
'P1W/1970-01-03T00:00:00Z': SUNDAY_BASED_WEEK, // 'week_ending_saturday'
|
||||
'P1W/1970-01-04T00:00:00Z': MONDAY_BASED_WEEK, // 'week_ending_sunday'
|
||||
};
|
||||
|
||||
return granularity in formats
|
||||
? getTimeFormatter(formats[granularity])
|
||||
: smartDateVerboseFormatter;
|
||||
}
|
||||
|
||||
export default function transformProps(chartProps) {
|
||||
const { width, height, formData, queryData } = chartProps;
|
||||
const {
|
||||
colorPicker,
|
||||
compareLag: compareLagInput,
|
||||
compareSuffix = '',
|
||||
headerFontSize,
|
||||
subheaderFontSize,
|
||||
metric,
|
||||
showTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader = '',
|
||||
vizType,
|
||||
} = formData;
|
||||
const granularity = formData.timeGrainSqla;
|
||||
let { yAxisFormat } = formData;
|
||||
const { data } = queryData;
|
||||
|
||||
let mainColor;
|
||||
if (colorPicker) {
|
||||
const { r, g, b } = colorPicker;
|
||||
mainColor = color.rgb(r, g, b).hex();
|
||||
}
|
||||
|
||||
let bigNumber;
|
||||
let trendLineData;
|
||||
const metricName = metric?.label ? metric.label : metric;
|
||||
const compareLag = Number(compareLagInput) || 0;
|
||||
const supportTrendLine = vizType === 'big_number';
|
||||
const supportAndShowTrendLine = supportTrendLine && showTrendLine;
|
||||
let percentChange = 0;
|
||||
let formattedSubheader = subheader;
|
||||
if (supportTrendLine) {
|
||||
const sortedData = [...data].sort((a, b) => a[TIME_COLUMN] - b[TIME_COLUMN]);
|
||||
bigNumber = sortedData.length === 0 ? null : sortedData[sortedData.length - 1][metricName];
|
||||
if (compareLag > 0) {
|
||||
const compareIndex = sortedData.length - (compareLag + 1);
|
||||
if (compareIndex >= 0) {
|
||||
const compareValue = sortedData[compareIndex][metricName];
|
||||
percentChange =
|
||||
compareValue === 0 ? 0 : (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
const formatPercentChange = getNumberFormatter(NumberFormats.PERCENT_SIGNED_1_POINT);
|
||||
formattedSubheader = `${formatPercentChange(percentChange)} ${compareSuffix}`;
|
||||
}
|
||||
}
|
||||
trendLineData = supportAndShowTrendLine
|
||||
? sortedData.map(point => ({
|
||||
x: point[TIME_COLUMN],
|
||||
y: point[metricName],
|
||||
}))
|
||||
: null;
|
||||
} else {
|
||||
bigNumber = data.length === 0 ? null : data[0][metricName];
|
||||
trendLineData = null;
|
||||
}
|
||||
|
||||
let className = '';
|
||||
if (percentChange > 0) {
|
||||
className = 'positive';
|
||||
} else if (percentChange < 0) {
|
||||
className = 'negative';
|
||||
}
|
||||
|
||||
if (!yAxisFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
||||
chartProps.datasource.metrics.forEach(metricEntry => {
|
||||
if (metricEntry.metric_name === metric && metricEntry.d3format) {
|
||||
yAxisFormat = metricEntry.d3format;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const formatDate = getTimeFormatterForGranularity(granularity);
|
||||
const formatValue = getNumberFormatter(yAxisFormat);
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
bigNumber,
|
||||
className,
|
||||
formatBigNumber: formatValue,
|
||||
headerFontSize,
|
||||
subheaderFontSize,
|
||||
mainColor,
|
||||
renderTooltip: renderTooltipFactory(formatDate, formatValue),
|
||||
showTrendLine: supportAndShowTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader: formattedSubheader,
|
||||
trendLineData,
|
||||
};
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import * as color from 'd3-color';
|
||||
import { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';
|
||||
import { ChartProps } from '@superset-ui/chart';
|
||||
import getTimeFormatterForGranularity from '../utils/getTimeFormatterForGranularity';
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
const formatPercentChange = getNumberFormatter(NumberFormats.PERCENT_SIGNED_1_POINT);
|
||||
|
||||
// we trust both the x (time) and y (big number) to be numeric
|
||||
type BigNumberDatum = {
|
||||
[TIME_COLUMN]: number;
|
||||
[key: string]: number | null;
|
||||
};
|
||||
|
||||
export default function transformProps(chartProps: ChartProps) {
|
||||
const { width, height, formData, queryData } = chartProps;
|
||||
const {
|
||||
colorPicker,
|
||||
compareLag: compareLagInput,
|
||||
compareSuffix = '',
|
||||
headerFontSize,
|
||||
metric,
|
||||
showTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader = '',
|
||||
subheaderFontSize,
|
||||
timeGrainSqla: granularity,
|
||||
vizType,
|
||||
timeRangeFixed = false,
|
||||
} = formData;
|
||||
let { yAxisFormat } = formData;
|
||||
const { data, from_dttm: fromDatetime, to_dttm: toDatetime } = queryData;
|
||||
const metricName = metric?.label ? metric.label : metric;
|
||||
const compareLag = Number(compareLagInput) || 0;
|
||||
const supportTrendLine = vizType === 'big_number';
|
||||
const supportAndShowTrendLine = supportTrendLine && showTrendLine;
|
||||
let formattedSubheader = subheader;
|
||||
|
||||
let mainColor;
|
||||
if (colorPicker) {
|
||||
const { r, g, b } = colorPicker;
|
||||
mainColor = color.rgb(r, g, b).hex();
|
||||
}
|
||||
|
||||
let trendLineData;
|
||||
let percentChange = 0;
|
||||
let bigNumber = data.length === 0 ? null : data[0][metricName];
|
||||
let bigNumberFallback;
|
||||
|
||||
if (data.length > 0) {
|
||||
const sortedData = (data as BigNumberDatum[])
|
||||
.map(d => ({ x: d[TIME_COLUMN], y: d[metricName] }))
|
||||
.sort((a, b) => b.x - a.x); // sort in time descending order
|
||||
|
||||
bigNumber = sortedData[0].y;
|
||||
if (bigNumber === null) {
|
||||
bigNumberFallback = sortedData.find(d => d.y !== null);
|
||||
bigNumber = bigNumberFallback ? bigNumberFallback.y : null;
|
||||
}
|
||||
|
||||
if (compareLag > 0) {
|
||||
const compareIndex = compareLag;
|
||||
if (compareIndex < sortedData.length) {
|
||||
const compareValue = sortedData[compareIndex].y;
|
||||
// compare values must both be non-nulls
|
||||
if (bigNumber !== null && compareValue !== null && compareValue !== 0) {
|
||||
percentChange = (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
formattedSubheader = `${formatPercentChange(percentChange)} ${compareSuffix}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (supportTrendLine) {
|
||||
// must reverse to ascending order otherwise it confuses tooltip triggers
|
||||
sortedData.reverse();
|
||||
trendLineData = supportAndShowTrendLine ? sortedData : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let className = '';
|
||||
if (percentChange > 0) {
|
||||
className = 'positive';
|
||||
} else if (percentChange < 0) {
|
||||
className = 'negative';
|
||||
}
|
||||
|
||||
if (!yAxisFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
||||
chartProps.datasource.metrics.forEach(
|
||||
// eslint-disable-next-line camelcase
|
||||
(metricEntry: { metric_name?: string; d3format: string }) => {
|
||||
if (metricEntry.metric_name === metric && metricEntry.d3format) {
|
||||
yAxisFormat = metricEntry.d3format;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const formatNumber = getNumberFormatter(yAxisFormat);
|
||||
const formatTime = getTimeFormatterForGranularity(granularity);
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
bigNumber,
|
||||
bigNumberFallback,
|
||||
className,
|
||||
formatNumber,
|
||||
formatTime,
|
||||
headerFontSize,
|
||||
subheaderFontSize,
|
||||
mainColor,
|
||||
showTrendLine: supportAndShowTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader: formattedSubheader,
|
||||
trendLineData,
|
||||
fromDatetime,
|
||||
toDatetime,
|
||||
timeRangeFixed,
|
||||
};
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import transformProps from '../BigNumber/transformProps';
|
||||
|
||||
const formData = {
|
||||
metric: 'value',
|
||||
colorPicker: {
|
||||
r: 0,
|
||||
g: 122,
|
||||
b: 135,
|
||||
a: 1,
|
||||
},
|
||||
compareLag: 1,
|
||||
timeGrainSqla: 'P0.25Y',
|
||||
compareSuffix: 'over last quarter',
|
||||
vizType: 'big_number',
|
||||
yAxisFormat: '.3s',
|
||||
};
|
||||
|
||||
function generateProps(data: object[], extraFormData = {}, extraQueryData = {}) {
|
||||
return {
|
||||
width: 200,
|
||||
height: 500,
|
||||
annotationData: {},
|
||||
datasource: {
|
||||
columnFormats: {},
|
||||
verboseMap: {},
|
||||
},
|
||||
rawDatasource: {},
|
||||
rawFormData: {},
|
||||
hooks: {},
|
||||
initialValues: {},
|
||||
formData: {
|
||||
...formData,
|
||||
...extraFormData,
|
||||
},
|
||||
queryData: {
|
||||
data,
|
||||
...extraQueryData,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('BigNumber', () => {
|
||||
describe('transformProps()', () => {
|
||||
const props = generateProps(
|
||||
[
|
||||
{
|
||||
__timestamp: 0,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
__timestamp: 100,
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
{ showTrendLine: true },
|
||||
);
|
||||
const transformed = transformProps(props);
|
||||
|
||||
it('timeRangeUseFallback', () => {
|
||||
// the first item is the last item sorted by __timestamp
|
||||
const lastDatum = transformed.trendLineData?.pop();
|
||||
expect(lastDatum?.x).toStrictEqual(100);
|
||||
expect(lastDatum?.y).toBeNull();
|
||||
expect(transformed.bigNumber).toStrictEqual(1);
|
||||
expect(transformed.bigNumberFallback).not.toBeNull();
|
||||
});
|
||||
|
||||
it('formatTime by ganularity', () => {
|
||||
expect(transformed.formatTime(new Date('2020-01-01'))).toStrictEqual('2020 Q1');
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
declare module '@data-ui/xy-chart';
|
||||
declare module '*.png';
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { getTimeFormatter, TimeFormats, smartDateVerboseFormatter } from '@superset-ui/time-format';
|
||||
|
||||
// Translate time granularity to d3-format
|
||||
const MINUTE = '%Y-%m-%d %H:%M';
|
||||
const SUNDAY_BASED_WEEK = '%Y W%U';
|
||||
const MONDAY_BASED_WEEK = '%Y W%W';
|
||||
const { DATABASE_DATE, DATABASE_DATETIME } = TimeFormats;
|
||||
|
||||
// search for `builtin_time_grains` in incubator-superset/superset/db_engine_specs/base.py
|
||||
const formats = {
|
||||
date: DATABASE_DATE,
|
||||
PT1S: DATABASE_DATETIME, // second
|
||||
PT1M: MINUTE, // minute
|
||||
PT5M: MINUTE, // 5 minute
|
||||
PT10M: MINUTE, // 10 minute
|
||||
PT15M: MINUTE, // 15 minute
|
||||
'PT0.5H': MINUTE, // half hour
|
||||
PT1H: '%Y-%m-%d %H:00', // hour
|
||||
P1D: DATABASE_DATE, // day
|
||||
P1W: SUNDAY_BASED_WEEK, // week
|
||||
P1M: '%Y-%m', // month
|
||||
'P0.25Y': '%Y Q%q', // quarter
|
||||
P1Y: '%Y', // year
|
||||
// d3-time-format weeks does not support weeks start on Sunday
|
||||
'1969-12-28T00:00:00Z/P1W': SUNDAY_BASED_WEEK, // 'week_start_sunday'
|
||||
'1969-12-29T00:00:00Z/P1W': MONDAY_BASED_WEEK, // 'week_start_monday'
|
||||
'P1W/1970-01-03T00:00:00Z': SUNDAY_BASED_WEEK, // 'week_ending_saturday'
|
||||
'P1W/1970-01-04T00:00:00Z': MONDAY_BASED_WEEK, // 'week_ending_sunday'
|
||||
};
|
||||
|
||||
type TimeGranularity =
|
||||
| 'date'
|
||||
| 'PT1S'
|
||||
| 'PT1M'
|
||||
| 'PT5M'
|
||||
| 'PT10M'
|
||||
| 'PT15M'
|
||||
| 'PT0.5H'
|
||||
| 'PT1H'
|
||||
| 'P1D'
|
||||
| 'P1W'
|
||||
| 'P0.25Y'
|
||||
| 'P1Y'
|
||||
| '1969-12-28T00:00:00Z/P1W'
|
||||
| '1969-12-29T00:00:00Z/P1W'
|
||||
| 'P1W/1970-01-03T00:00:00Z';
|
||||
|
||||
export default function getTimeFormatterForGranularity(granularity: TimeGranularity) {
|
||||
return granularity in formats
|
||||
? getTimeFormatter(formats[granularity])
|
||||
: smartDateVerboseFormatter;
|
||||
}
|
@ -47,7 +47,7 @@ const EMPTY_EXAMPLES = [
|
||||
* { storyPath: string, storyName: string, renderStory: fn() => node }
|
||||
*
|
||||
*/
|
||||
const requireContext = require.context('./', /* subdirs= */ true, /index\.jsx?$/);
|
||||
const requireContext = require.context('./', /* subdirs= */ true, /index\.(js|ts)x?$/);
|
||||
|
||||
requireContext.keys().forEach(packageName => {
|
||||
const packageExport = requireContext(packageName);
|
||||
|
@ -1,8 +1,43 @@
|
||||
/* eslint-disable no-magic-numbers, sort-keys */
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SuperChart } from '@superset-ui/chart';
|
||||
import testData from './data';
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
|
||||
const formData = {
|
||||
colorPicker: {
|
||||
r: 0,
|
||||
g: 122,
|
||||
b: 135,
|
||||
a: 1,
|
||||
},
|
||||
compareLag: 1,
|
||||
compareSuffix: 'over 10Y',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
showTrendLine: true,
|
||||
startYAxisAtZero: true,
|
||||
vizType: 'big_number',
|
||||
yAxisFormat: '.3s',
|
||||
};
|
||||
|
||||
/**
|
||||
* Add null values to trendline data
|
||||
* @param data input data
|
||||
@ -24,21 +59,7 @@ export default [
|
||||
width={400}
|
||||
height={400}
|
||||
queryData={{ data: testData }}
|
||||
formData={{
|
||||
colorPicker: {
|
||||
r: 0,
|
||||
g: 122,
|
||||
b: 135,
|
||||
a: 1,
|
||||
},
|
||||
compareLag: 1,
|
||||
compareSuffix: 'over 10Y',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
showTrendLine: true,
|
||||
startYAxisAtZero: true,
|
||||
vizType: 'big_number',
|
||||
yAxisFormat: '.3s',
|
||||
}}
|
||||
formData={formData}
|
||||
/>
|
||||
),
|
||||
storyName: 'Basic with Trendline',
|
||||
@ -51,21 +72,7 @@ export default [
|
||||
width={400}
|
||||
height={400}
|
||||
queryData={{ data: withNulls(testData, 3) }}
|
||||
formData={{
|
||||
colorPicker: {
|
||||
r: 0,
|
||||
g: 122,
|
||||
b: 135,
|
||||
a: 1,
|
||||
},
|
||||
compareLag: 1,
|
||||
compareSuffix: 'over 10Y',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
showTrendLine: true,
|
||||
startYAxisAtZero: true,
|
||||
vizType: 'big_number',
|
||||
yAxisFormat: '.3s',
|
||||
}}
|
||||
formData={formData}
|
||||
/>
|
||||
),
|
||||
storyName: 'Null in the middle',
|
||||
@ -77,26 +84,39 @@ export default [
|
||||
chartType="big-number"
|
||||
width={400}
|
||||
height={400}
|
||||
queryData={{ data: testData.slice(0, 9) }}
|
||||
queryData={{
|
||||
data: testData.slice(0, 9),
|
||||
from_dttm: testData[testData.length - 1][TIME_COLUMN],
|
||||
to_dttm: null,
|
||||
}}
|
||||
formData={{
|
||||
colorPicker: {
|
||||
r: 0,
|
||||
g: 122,
|
||||
b: 135,
|
||||
a: 1,
|
||||
},
|
||||
timeGrainSqla: 'P0.25Y',
|
||||
compareLag: 1,
|
||||
compareSuffix: 'over 10Y',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
showTrendLine: true,
|
||||
startYAxisAtZero: true,
|
||||
vizType: 'big_number',
|
||||
yAxisFormat: '.3s',
|
||||
...formData,
|
||||
timeGrainSqla: 'P1Y',
|
||||
timeRangeFixed: true,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
storyName: 'Missing head',
|
||||
storyName: 'Missing range start (fix time range)',
|
||||
storyPath: 'legacy-|preset-chart-big-number|BigNumberChartPlugin',
|
||||
},
|
||||
{
|
||||
renderStory: () => (
|
||||
<SuperChart
|
||||
chartType="big-number"
|
||||
width={400}
|
||||
height={400}
|
||||
queryData={{
|
||||
data: testData.slice(0, 9),
|
||||
from_dttm: testData[testData.length - 1][TIME_COLUMN],
|
||||
to_dttm: testData[0][TIME_COLUMN],
|
||||
}}
|
||||
formData={{
|
||||
...formData,
|
||||
timeRangeFixed: false,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
storyName: `Missing range start (don't fix range)`,
|
||||
storyPath: 'legacy-|preset-chart-big-number|BigNumberChartPlugin',
|
||||
},
|
||||
];
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { BigNumberChartPlugin } from '../../../../../superset-ui-legacy-preset-chart-big-number';
|
||||
import Stories from './Stories.tsx';
|
||||
|
||||
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
||||
|
||||
export default {
|
||||
examples: [...Stories],
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { BigNumberChartPlugin } from '../../../../../superset-ui-legacy-preset-chart-big-number/src';
|
||||
import Stories from './Stories';
|
||||
|
||||
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
||||
|
||||
export default {
|
||||
examples: [...Stories],
|
||||
};
|
@ -1,4 +1,21 @@
|
||||
/* eslint-disable no-magic-numbers */
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SuperChart } from '@superset-ui/chart';
|
||||
import data from './data';
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable sort-keys */
|
||||
export default [
|
||||
{
|
||||
sum__num: 32546308,
|
@ -1,8 +0,0 @@
|
||||
import { BigNumberTotalChartPlugin } from '../../../../../superset-ui-legacy-preset-chart-big-number';
|
||||
import Stories from './Stories';
|
||||
|
||||
new BigNumberTotalChartPlugin().configure({ key: 'big-number-total' }).register();
|
||||
|
||||
export default {
|
||||
examples: [...Stories],
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { BigNumberTotalChartPlugin } from '../../../../../superset-ui-legacy-preset-chart-big-number/src';
|
||||
import Stories from './Stories';
|
||||
|
||||
new BigNumberTotalChartPlugin().configure({ key: 'big-number-total' }).register();
|
||||
|
||||
export default {
|
||||
examples: [...Stories],
|
||||
};
|
@ -1,14 +1,14 @@
|
||||
/**
|
||||
* Build only plugins specified by globs
|
||||
*/
|
||||
const { spawnSync, spawn } = require('child_process');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const glob = process.argv[2];
|
||||
const extraArgs = process.argv.slice(2);
|
||||
|
||||
process.env.PATH = `./node_modules/.bin:${process.env.PATH}`;
|
||||
|
||||
const run = (cmd) => {
|
||||
const run = cmd => {
|
||||
console.log(`>> ${cmd}`);
|
||||
const [p, ...args] = cmd.split(' ');
|
||||
const runner = spawnSync;
|
||||
|
@ -3489,7 +3489,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.8.tgz#aa9552c570a96e33c132e0fd20e331f64baa9dd5"
|
||||
integrity sha512-y5lGlazdc0HNO0F3UUX2DPE7OmYvd9Kcym4hXwrJcNUkDaypR5pX+apuMikl9LfTxKItJsY9KYvzBulpCKyvuQ==
|
||||
|
||||
"@types/d3-color@*":
|
||||
"@types/d3-color@*", "@types/d3-color@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.2.2.tgz#80cf7cfff7401587b8f89307ba36fe4a576bc7cf"
|
||||
integrity sha512-6pBxzJ8ZP3dYEQ4YjQ+NVbQaOflfgXq/JbDiS99oLobM2o72uAST4q6yPxHv6FOTCRC/n35ktuo8pvw/S4M7sw==
|
||||
@ -3954,6 +3954,11 @@
|
||||
"@types/prop-types" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@types/shortid@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b"
|
||||
integrity sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=
|
||||
|
||||
"@types/sizzle@*":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
|
||||
|
Loading…
Reference in New Issue
Block a user