chore: cleanup big number and table chart (#422)

They are moved to superset-ui by apache-superset/superset-ui#333
This commit is contained in:
Jianchao Yang 2020-04-02 22:50:33 -07:00 committed by Yongjie Zhao
parent 0176d1dd04
commit 7497a61be6
37 changed files with 0 additions and 27127 deletions

View File

@ -1,33 +0,0 @@
## @superset-ui/legacy-plugin-chart-table
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-table.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-table.svg?style=flat-square)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-plugin-chart-table&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-legacy-plugin-chart-table)
This plugin provides Table for Superset.
### Usage
Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to
lookup this chart throughout the app.
```js
import TableChartPlugin from '@superset-ui/legacy-plugin-chart-table';
new TableChartPlugin().configure({ key: 'table' }).register();
```
Then use it via `SuperChart`. See
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-table)
for more details.
```js
<SuperChart
chartType="table"
width={600}
height={600}
formData={...}
queryData={{
data: {...},
}}
/>
```

View File

@ -1,47 +0,0 @@
{
"name": "@superset-ui/legacy-plugin-chart-table",
"version": "0.11.20",
"description": "Superset Legacy Chart - Table",
"sideEffects": [
"*.css"
],
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui-plugins.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui-plugins/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui-plugins#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"datatables.net-bs": "^1.10.20",
"xss": "^1.0.6"
},
"devDependencies": {
"@types/datatables.net": "^1.10.18"
},
"peerDependencies": {
"@superset-ui/chart": "^0.12.0",
"@superset-ui/number-format": "^0.12.10",
"@superset-ui/query": "^0.12.8",
"@superset-ui/time-format": "^0.12.0",
"@superset-ui/translation": "^0.12.0",
"jquery": "^3.4.1",
"react": "^16.8.0",
"react-dom": "^16.8.0"
}
}

View File

@ -1,264 +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 { t } from '@superset-ui/translation';
import React, { useEffect, createRef } from 'react';
import ReactDOMServer from 'react-dom/server';
import { formatNumber, NumberFormats } from '@superset-ui/number-format';
import { getTimeFormatter } from '@superset-ui/time-format';
import { filterXSS } from 'xss';
// initialize datatables.net
import $ from 'jquery';
import dt from 'datatables.net-bs/js/dataTables.bootstrap';
import 'datatables.net-bs/css/dataTables.bootstrap.css';
import './Table.css';
import { DataTableProps } from './transformProps';
// Depending on how the modules are imported, `dt` may be a CommonJS init function,
// or the DataTable class itself. In case it is the former, we'd need to tell it
// where is jQuery.
if (!dt.$) {
dt(window, $);
}
const { PERCENT_3_POINT } = NumberFormats;
const isProbablyHTML = (text: string) => /<[^>]+>/.test(text);
export default function ReactDataTable(props: DataTableProps) {
const {
data,
height,
alignPositiveNegative = false,
colorPositiveNegative = false,
columns,
includeSearch = false,
metrics: aggMetrics,
pageLength,
percentMetrics,
tableTimestampFormat,
// orderDesc,
// TODO: add back the broken dashboard filters feature
// filters = {},
// onAddFilter = NOOP,
// onRemoveFilter = NOOP,
// tableFilter,
// timeseriesLimitMetric,
} = props;
const formatTimestamp = getTimeFormatter(tableTimestampFormat);
const metrics = (aggMetrics || [])
.concat(percentMetrics || [])
// actual records must be of numeric types as well
.filter(m => data[0] && typeof data[0][m] === 'number');
// check whethere a key is a metric
const metricsSet = new Set(aggMetrics);
const percentMetricsSet = new Set(percentMetrics);
// collect min/max for rendering bars
const maxes: { [key: string]: number } = {};
const mins: { [key: string]: number } = {};
columns.forEach(({ key }) => {
const vals = data.map(row => row[key]);
if (metrics.includes(key)) {
const nums = vals as number[];
if (alignPositiveNegative) {
maxes[key] = Math.max(...nums.map(Math.abs));
} else {
maxes[key] = Math.max(...nums);
mins[key] = Math.min(...nums);
}
}
});
const viewportHeight = Math.min(height, window.innerHeight);
const pageLengthChoices = [10, 25, 40, 50, 75, 100, 150, 200];
const hasPagination = pageLength > 0;
const rootElem = createRef<HTMLDivElement>();
/**
* Adjust styles after rendering the table
*/
function drawCallback(this: DataTables.JQueryDataTables) {
const root = rootElem.current as HTMLElement;
// force smaller pagination, because datatables-bs hard-corded pagination styles
$('.pagination', root).addClass('pagination-sm');
// display tr rows on current page
$('tr', root).css('display', '');
}
/**
* Format text for cell value
*/
function cellText(key: string, format: string | undefined, val: unknown) {
if (key === '__timestamp') {
return formatTimestamp(val);
}
if (typeof val === 'string') {
return filterXSS(val, { stripIgnoreTag: true });
}
if (percentMetricsSet.has(key)) {
// in case percent metric can specify percent format in the future
return formatNumber(format || PERCENT_3_POINT, val as number);
}
if (metricsSet.has(key)) {
// default format '' will return human readable numbers (e.g. 50M, 33k)
return formatNumber(format, val as number);
}
return val;
}
/**
* Cell background to render columns as horizontal bar chart
*/
function cellBar(key: string, val: number) {
const r = colorPositiveNegative && val < 0 ? 150 : 0;
if (alignPositiveNegative) {
const perc = Math.abs(Math.round((val / maxes[key]) * 100));
// The 0.01 to 0.001 is a workaround for what appears to be a
// CSS rendering bug on flat, transparent colors
return (
`linear-gradient(to right, rgba(${r},0,0,0.2), rgba(${r},0,0,0.2) ${perc}%, ` +
`rgba(0,0,0,0.01) ${perc}%, rgba(0,0,0,0.001) 100%)`
);
}
const posExtent = Math.abs(Math.max(maxes[key], 0));
const negExtent = Math.abs(Math.min(mins[key], 0));
const tot = posExtent + negExtent;
const perc1 = Math.round((Math.min(negExtent + val, negExtent) / tot) * 100);
const perc2 = Math.round((Math.abs(val) / tot) * 100);
// The 0.01 to 0.001 is a workaround for what appears to be a
// CSS rendering bug on flat, transparent colors
return (
`linear-gradient(to right, rgba(0,0,0,0.01), rgba(0,0,0,0.001) ${perc1}%, ` +
`rgba(${r},0,0,0.2) ${perc1}%, rgba(${r},0,0,0.2) ${perc1 + perc2}%, ` +
`rgba(0,0,0,0.01) ${perc1 + perc2}%, rgba(0,0,0,0.001) 100%)`
);
}
const options = {
aaSorting: [], // initial sorting order, reset to [] to use backend ordering
autoWidth: false,
paging: hasPagination,
pagingType: 'first_last_numbers',
pageLength,
lengthMenu: [
[...pageLengthChoices, -1],
[...pageLengthChoices, t('All')],
],
searching: includeSearch,
language: {
paginate: {
first: t('First'),
last: t('Last'),
previous: t('Previous'),
next: t('Next'),
},
},
bInfo: false,
scrollY: `${viewportHeight}px`,
scrollCollapse: true,
scrollX: true,
drawCallback,
};
useEffect(() => {
const $root = $(rootElem.current as HTMLElement);
const dataTable = $root.find('table').DataTable(options);
// adjust table height
const scrollHeadHeight = $root.find('.dataTables_scrollHead').height() || 0;
const paginationHeight = $root.find('.dataTables_paginate').height() || 0;
const searchBarHeight =
$root
.find('.dataTables_length,.dataTables_filter')
.closest('.row')
.height() || 0;
const scrollBodyHeight = viewportHeight - scrollHeadHeight - paginationHeight - searchBarHeight;
$root.find('.dataTables_scrollBody').css('max-height', scrollBodyHeight);
return () => {
// there may be weird lifecycle issues, so put destroy in try/catch
try {
dataTable.destroy();
// reset height
$root.find('.dataTables_scrollBody').css('max-height', '');
} catch (error) {
// pass
}
};
});
const tableElement = (
<table className="table table-striped table-condensed table-hover">
<thead>
<tr>
{columns.map(col => (
// by default all columns will have sorting
<th key={col.key} className="sorting" title={col.label}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((record, i) => (
<tr
// eslint-disable-next-line react/no-array-index-key
key={i}
// hide rows after first page makes the initial render faster (less layout computation)
style={{ display: pageLength > 0 && i >= pageLength ? 'none' : undefined }}
>
{columns.map(({ key, format }) => {
const val = record[key];
const keyIsMetric = metricsSet.has(key);
const text = cellText(key, format, val);
const isHtml = !keyIsMetric && isProbablyHTML(text);
return (
<td
key={key}
// only set innerHTML for actual html content, this saves time
dangerouslySetInnerHTML={isHtml ? { __html: text } : undefined}
data-sort={val}
className={keyIsMetric ? 'text-right' : ''}
style={{
backgroundImage: keyIsMetric ? cellBar(key, val as number) : undefined,
}}
title={keyIsMetric || percentMetricsSet.has(key) ? String(val) : ''}
>
{isHtml ? null : text}
</td>
);
})}
</tr>
))}
</tbody>
</table>
);
return (
<div
dangerouslySetInnerHTML={{ __html: ReactDOMServer.renderToStaticMarkup(tableElement) }}
ref={rootElem}
className="superset-legacy-chart-table"
/>
);
}

View File

@ -1,43 +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.
*/
.superset-legacy-chart-table {
margin: 0px auto;
}
.superset-legacy-chart-table table {
width: 100%;
}
.superset-legacy-chart-table .dt-metric {
text-align: right;
}
.superset-legacy-chart-table div.dataTables_wrapper div.dataTables_paginate {
line-height: 0;
}
.superset-legacy-chart-table div.dataTables_wrapper div.dataTables_paginate ul.pagination {
margin-top: 0.5em;
}
.superset-legacy-chart-table table.table thead th.sorting:after,
.superset-legacy-chart-table table.table thead th.sorting_asc:after,
.superset-legacy-chart-table table.table thead th.sorting_desc:after {
top: auto;
bottom: 6px;
}
.superset-legacy-chart-table td {
white-space: pre-wrap;
}

View File

@ -1,40 +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 { t } from '@superset-ui/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
const metadata = new ChartMetadata({
canBeAnnotationTypes: ['EVENT', 'INTERVAL'],
description: '',
name: t('Table'),
thumbnail,
useLegacyApi: true,
});
export default class TableChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('./ReactDataTable'),
metadata,
transformProps,
});
}
}

View File

@ -1,114 +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 { ChartProps } from '@superset-ui/chart';
import { QueryFormDataMetric } from '@superset-ui/query';
interface DataRecord {
[key: string]: unknown;
}
interface DataColumnMeta {
// `key` is what is called `label` in the input props
key: string;
// `label` is verbose column name used for rendering
label: string;
format?: string;
}
export interface DataTableProps {
// Each object is { field1: value1, field2: value2 }
data: DataRecord[];
height: number;
alignPositiveNegative: boolean;
colorPositiveNegative: boolean;
columns: DataColumnMeta[];
metrics: string[];
percentMetrics: string[];
includeSearch: boolean;
orderDesc: boolean;
pageLength: number;
tableTimestampFormat: string;
// TODO: add filters back or clean up
// filters: object;
// onAddFilter?: (key: string, value: number[]) => void;
// onRemoveFilter?: (key: string, value: number[]) => void;
// tableFilter: boolean;
// timeseriesLimitMetric: string | object;
}
/**
* Consolidate list of metrics to string, identified by its unique identifier
*/
const consolidateMetricShape = (metric: QueryFormDataMetric) => {
if (typeof metric === 'string') return metric;
// even thought `metric.optionName` is more unique, it's not used
// anywhere else in `queryData` and cannot be used to access `data.records`.
// The records are still keyed by `metric.label`.
return metric.label;
};
export default function transformProps(chartProps: ChartProps): DataTableProps {
const { height, datasource, formData, queryData } = chartProps;
const {
alignPn,
colorPn,
includeSearch,
orderDesc,
pageLength,
metrics: metrics_,
percentMetrics: percentMetrics_,
tableTimestampFormat,
} = formData;
const { columnFormats, verboseMap } = datasource;
const { records, columns: columns_ } = queryData.data;
const metrics = (metrics_ ?? []).map(consolidateMetricShape);
// percent metrics always starts with a '%' sign.
const percentMetrics = (percentMetrics_ ?? [])
.map(consolidateMetricShape)
.map((x: string) => `%${x}`);
const columns = columns_.map((key: string) => {
let label = verboseMap[key] || key;
// make sure there is a " " after "%" for percent metrics
if (label[0] === '%' && label[1] !== ' ') {
label = `% ${label.slice(1)}`;
}
return {
key,
label,
format: columnFormats?.[key],
};
});
return {
height,
data: records,
columns,
metrics,
percentMetrics,
alignPositiveNegative: alignPn,
colorPositiveNegative: colorPn,
includeSearch,
orderDesc,
pageLength: pageLength && parseInt(pageLength, 10),
tableTimestampFormat,
};
}

View File

@ -1,63 +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 React from 'react';
import { mount, CommonWrapper } from 'enzyme';
import ReactDataTable from '../src/ReactDataTable';
import transformProps from '../src/transformProps';
import testData from './testData';
describe('legacy-table', () => {
// Can test more prop transformation here. Not needed for now.
describe('transformProps', () => {});
describe('ReactDataTable', () => {
let wrap: CommonWrapper; // the ReactDataTable wraper
it('render basic data', () => {
wrap = mount(<ReactDataTable {...transformProps(testData.basic)} />);
const tree = wrap.render(); // returns a CheerioWrapper with jQuery-like API
const cells = tree.find('td');
expect(tree.hasClass('superset-legacy-chart-table')).toEqual(true);
expect(cells).toHaveLength(4);
expect(cells.eq(0).text()).toEqual('Michael');
expect(cells.eq(3).attr('data-sort')).toEqual('2467');
});
it('render advanced data', () => {
// should successfull rerender with new props
wrap.setProps(transformProps(testData.advanced));
const tree = wrap.render();
const cells = tree.find('td');
expect(
tree
.find('th')
.eq(1)
.text(),
).toEqual('Sum of Num');
expect(cells.eq(2).text()).toEqual('12.346%');
expect(cells.eq(4).text()).toEqual('2.47k');
});
it('render empty data', () => {
wrap.setProps(transformProps(testData.empty));
const tree = wrap.render();
expect(tree.text()).toContain('No data available in table');
});
});
});

View File

@ -1,120 +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 { ChartProps } from '@superset-ui/chart';
const basicFormData = {
alignPn: false,
colorPn: false,
includeSearch: false,
orderDesc: true,
pageLength: 0,
metrics: [],
percentMetrics: null,
timeseriesLimitMetric: null,
tableFilter: false,
tableTimestampFormat: '%Y-%m-%d %H:%M:%S',
};
const basicChartProps = {
width: 200,
height: 500,
annotationData: {},
datasource: {
columnFormats: {},
verboseMap: {},
},
rawDatasource: {},
rawFormData: {},
hooks: {},
initialValues: {},
queryData: {
data: {
columns: [],
records: [],
},
},
formData: basicFormData,
};
/**
* Basic data input
*/
const basic: ChartProps = {
...basicChartProps,
queryData: {
data: {
columns: ['name', 'sum__num'],
records: [
{
name: 'Michael',
sum__num: 2467063,
'%pct_nice': 0.123456,
},
{
name: 'Joe',
sum__num: 2467,
'%pct_nice': 0.00001,
},
],
},
},
};
/**
* Advanced data input with
* - verbose map
* - metric columns
*/
const advanced: ChartProps = {
...basic,
datasource: {
columnFormats: {},
verboseMap: {
sum__num: 'Sum of Num',
},
},
formData: {
...basicFormData,
metrics: ['sum__num'],
percentMetrics: ['pct_nice'],
},
queryData: {
data: {
columns: ['name', 'sum__num', '%pct_nice'],
records: [...basic.queryData.data.records],
},
},
};
const empty = {
...advanced,
queryData: {
...advanced.queryData,
data: {
...advanced.queryData.data,
records: [],
},
},
};
export default {
basic,
advanced,
empty,
};

View File

@ -1,2 +0,0 @@
declare module 'datatables.net-bs/js/dataTables.bootstrap';
declare module '*.png';

View File

@ -1,43 +0,0 @@
## @superset-ui/legacy-preset-chart-big-number
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-big-number.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-big-number.svg?style=flat-square)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-preset-chart-big-number&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-legacy-preset-chart-big-number)
This plugin provides Big Number for Superset.
### Usage
Import the preset and register. This will register the `BigNumber` and `BigNumberTotal` charts with key `big-number` and `big-number-total`, respectively.
```js
import { BigNumberChartPreset } from '@superset-ui/legacy-preset-chart-big-number';
new BigNumberChartPreset().register();
```
or register charts one by one. Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to lookup this chart throughout the app.
```js
import { BigNumberChartPlugin, BigNumberTotalChartPlugin } from '@superset-ui/legacy-preset-chart-big-number';
new BigNumberChartPlugin()
.configure({ key: 'big-number' })
.register();
new BigNumberTotalChartPlugin()
.configure({ key: 'big-number-total' })
.register();
```
Then use it via `SuperChart`. See [storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-big-number) for more details.
```js
<SuperChart
chartType="big-number"
width={600}
height={600}
formData={...}
queryData={{
data: {...},
}}
/>
```

View File

@ -1,47 +0,0 @@
{
"name": "@superset-ui/legacy-preset-chart-big-number",
"version": "0.11.21",
"description": "Superset Legacy Chart - Big Number",
"sideEffects": [
"*.css"
],
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui-plugins.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui-plugins/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui-plugins#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@data-ui/xy-chart": "^0.0.84",
"@types/d3-color": "^1.2.2",
"@types/shortid": "^0.0.29",
"d3-color": "^1.2.3",
"shortid": "^2.2.14"
},
"peerDependencies": {
"@superset-ui/chart": "^0.12.0",
"@superset-ui/color": "^0.12.0",
"@superset-ui/core": "^0.12.0",
"@superset-ui/dimension": "^0.12.0",
"@superset-ui/number-format": "^0.12.0",
"@superset-ui/time-format": "^0.12.0",
"@superset-ui/translation": "^0.12.0",
"react": "^15 || ^16"
}
}

View File

@ -1,72 +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.
*/
.superset-legacy-chart-big-number {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
Open Sans, Helvetica Neue, sans-serif;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
}
.superset-legacy-chart-big-number.no-trendline .subheader-line {
padding-bottom: 0.3em;
}
.superset-legacy-chart-big-number .text-container {
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 {
position: relative;
line-height: 1em;
font-weight: 600;
}
.superset-legacy-chart-big-number .header-line span {
position: absolute;
bottom: 0;
}
.superset-legacy-chart-big-number .subheader-line {
line-height: 1em;
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;
}

View File

@ -1,305 +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 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;

View File

@ -1,39 +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 { t } from '@superset-ui/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
const metadata = new ChartMetadata({
description: '',
name: t('Big Number with Trendline'),
thumbnail,
useLegacyApi: true,
});
export default class BigNumberChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('./BigNumber'),
metadata,
transformProps,
});
}
}

View File

@ -1,138 +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 { 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,
};
}

View File

@ -1,39 +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 { t } from '@superset-ui/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
import transformProps from '../BigNumber/transformProps';
import thumbnail from './images/thumbnail.png';
const metadata = new ChartMetadata({
description: '',
name: t('Big Number'),
thumbnail,
useLegacyApi: true,
});
export default class BigNumberTotalChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('../BigNumber/BigNumber'),
metadata,
transformProps,
});
}
}

View File

@ -1,3 +0,0 @@
export { default as BigNumberChartPlugin } from './BigNumber/index';
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal/index';
export { default as BigNumberChartPreset } from './preset';

View File

@ -1,33 +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 { Preset } from '@superset-ui/core';
import BigNumberChartPlugin from './BigNumber';
import BigNumberTotalChartPlugin from './BigNumberTotal';
export default class BigNumberChartPreset extends Preset {
constructor() {
super({
name: 'BigNumber charts',
plugins: [
new BigNumberChartPlugin().configure({ key: 'big_number' }),
new BigNumberTotalChartPlugin().configure({ key: 'big_number_total' }),
],
});
}
}

View File

@ -1,90 +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 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');
});
});
});

View File

@ -1,20 +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.
*/
declare module '@data-ui/xy-chart';
declare module '*.png';

View File

@ -1,70 +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 { 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;
}

View File

@ -1,158 +0,0 @@
/* eslint-disable no-magic-numbers */
import React, { useState } from 'react';
import { SuperChart } from '@superset-ui/chart';
import { Props as SuperChartProps } from '@superset-ui/chart/lib/components/SuperChart';
import data from './data';
import birthNames from './birth_names.json';
import 'bootstrap/dist/css/bootstrap.min.css';
function paginated(props: SuperChartProps, pageSize = 50) {
if (props.formData) {
props.formData = {
...props.formData,
page_length: pageSize,
};
}
if (props.queryData?.form_data) {
props.queryData.form_data = {
...props.queryData.form_data,
page_length: pageSize,
};
}
return {
...props,
};
}
function adjustNumCols(props: SuperChartProps, numCols = 7) {
const newProps = { ...props };
if (props.queryData) {
const { columns } = props.queryData.data;
const curSize = columns.length;
const newColumns = [...Array(numCols)].map((_, i) => {
return columns[i % curSize];
});
newProps.queryData = {
...props.queryData,
data: {
...props.queryData.data,
columns: newColumns,
},
};
}
return newProps;
}
/**
* Load sample data for testing
* @param props the original props passed to SuperChart
* @param pageSize number of records perpage
* @param targetSize the target total number of records
*/
function loadData(props: SuperChartProps, pageSize = 50, targetSize = 5042) {
if (!props.queryData) return props;
const data = props.queryData && props.queryData.data;
if (data.records.length > 0) {
while (data.records.length < targetSize) {
const records = data.records;
data.records = records.concat(records).slice(0, targetSize);
}
}
props.height = window.innerHeight - 130;
return paginated(props, pageSize);
}
export default [
{
renderStory: () => (
<SuperChart
chartType="table"
width={400}
height={400}
datasource={{
columnFormats: {},
verboseMap: {
name: 'name',
sum__num: 'sum__num',
},
}}
filters={{}}
queryData={{ data }}
formData={{
alignPn: false,
colorPn: false,
includeSearch: false,
metrics: ['sum__num'],
orderDesc: true,
pageLength: 0,
percentMetrics: null,
tableFilter: false,
tableTimestampFormat: '%Y-%m-%d %H:%M:%S',
timeseriesLimitMetric: null,
}}
/>
),
storyName: 'Basic',
storyPath: 'legacy-|plugin-chart-table|TableChartPlugin',
},
{
renderStory() {
const initialProps = loadData(birthNames);
const [chartProps, setChartProps] = useState(initialProps);
const updatePageSize = (size: number) => {
setChartProps(paginated(initialProps, size));
};
const updateNumCols = (numCols: number) => {
setChartProps(adjustNumCols(initialProps, numCols));
};
return (
<div className="superset-body">
<div className="panel">
<div className="panel-heading form-inline">
<div className="form-group">
Initial page size:{' '}
<div className="btn-group btn-group-sm">
{[10, 25, 40, 50, 100, -1].map(pageSize => {
return (
<button
key={pageSize}
className="btn btn-default"
onClick={() => updatePageSize(pageSize)}
>
{pageSize > 0 ? pageSize : 'All'}
</button>
);
})}
</div>
</div>
<div className="form-group" style={{ marginLeft: 20 }}>
Number of columns:{' '}
<div className="btn-group btn-group-sm">
{[1, 3, 5, 7, 9].map(numCols => {
return (
<button
key={numCols}
className="btn btn-default"
onClick={() => updateNumCols(numCols)}
>
{numCols}
</button>
);
})}
</div>
</div>
</div>
<div className="panel-body">
<SuperChart {...chartProps} chartType="table" />
</div>
</div>
</div>
);
},
storyName: 'Big Table',
storyPath: 'legacy-|plugin-chart-table|TableChartPlugin',
},
];

View File

@ -1,46 +0,0 @@
/* eslint-disable sort-keys */
export default {
columns: ['name', 'sum__num'],
records: [
{
name: 'Michael',
sum__num: 2467063,
},
{
name: 'Christopher',
sum__num: 1725265,
},
{
name: 'David',
sum__num: 1570516,
},
{
name: 'James',
sum__num: 1506025,
},
{
name: 'John',
sum__num: 1426074,
},
{
name: 'Matthew',
sum__num: 1355803,
},
{
name: 'Robert',
sum__num: 1314800,
},
{
name: 'Daniel',
sum__num: 1159354,
},
{
name: 'Joseph',
sum__num: 1114098,
},
{
name: 'William',
sum__num: 1113701,
},
],
};

View File

@ -1,8 +0,0 @@
import TableChartPlugin from '../../../../superset-ui-legacy-plugin-chart-table';
import Stories from './Stories';
new TableChartPlugin().configure({ key: 'table' }).register();
export default {
examples: [...Stories],
};

View File

@ -1,122 +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 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
*/
function withNulls(origData: object[], nullPosition: number = 3) {
const data = [...origData];
data[nullPosition] = {
...data[nullPosition],
sum__SP_POP_TOTL: null,
};
return data;
}
export default [
{
renderStory: () => (
<SuperChart
chartType="big-number"
width={400}
height={400}
queryData={{ data: testData }}
formData={formData}
/>
),
storyName: 'Basic with Trendline',
storyPath: 'legacy-|preset-chart-big-number|BigNumberChartPlugin',
},
{
renderStory: () => (
<SuperChart
chartType="big-number"
width={400}
height={400}
queryData={{ data: withNulls(testData, 3) }}
formData={formData}
/>
),
storyName: 'Null in the middle',
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: null,
}}
formData={{
...formData,
timeGrainSqla: 'P1Y',
timeRangeFixed: true,
}}
/>
),
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',
},
];

View File

@ -1,59 +0,0 @@
/* eslint-disable sort-keys */
export default [
{
__timestamp: 1388534400000.0,
sum__SP_POP_TOTL: 7237260256.0,
},
{
__timestamp: 1356998400000.0,
sum__SP_POP_TOTL: 7151135481.0,
},
{
__timestamp: 1325376000000.0,
sum__SP_POP_TOTL: 7066007165.0,
},
{
__timestamp: 1293840000000.0,
sum__SP_POP_TOTL: 6984252419.0,
},
{
__timestamp: 1262304000000.0,
sum__SP_POP_TOTL: 6901110512.0,
},
{
__timestamp: 1230768000000.0,
sum__SP_POP_TOTL: 6818457192.0,
},
{
__timestamp: 1199145600000.0,
sum__SP_POP_TOTL: 6735914031.0,
},
{
__timestamp: 1167609600000.0,
sum__SP_POP_TOTL: 6653571302.0,
},
{
__timestamp: 1136073600000.0,
sum__SP_POP_TOTL: 6572596462.0,
},
{
__timestamp: 1104537600000.0,
sum__SP_POP_TOTL: 6491857539.0,
},
{
__timestamp: 1072915200000.0,
sum__SP_POP_TOTL: 6411615629.0,
},
{
__timestamp: 1041379200000.0,
sum__SP_POP_TOTL: 6331766837.0,
},
{
__timestamp: 1009843200000.0,
sum__SP_POP_TOTL: 6252469127.0,
},
{
__timestamp: 978307200000.0,
sum__SP_POP_TOTL: 617333941.0,
},
];

View File

@ -1,26 +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 { 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],
};

View File

@ -1,60 +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 React from 'react';
import { SuperChart } from '@superset-ui/chart';
import data from './data';
export default [
{
renderStory: () => (
<SuperChart
chartType="big-number-total"
width={400}
height={400}
queryData={{ data }}
formData={{
metric: 'sum__num',
subheader: 'total female participants',
vizType: 'big_number_total',
yAxisFormat: '.3s',
}}
/>
),
storyName: 'Basic',
storyPath: 'legacy-|preset-chart-big-number|BigNumberTotalChartPlugin',
},
{
renderStory: () => (
<SuperChart
chartType="big-number-total"
width={400}
height={400}
queryData={{ data: [] }}
formData={{
metric: 'sum__num',
subheader: 'total female participants',
vizType: 'big_number_total',
yAxisFormat: '.3s',
}}
/>
),
storyName: 'Basic No Data',
storyPath: 'legacy-|preset-chart-big-number|BigNumberTotalChartPlugin',
},
];

View File

@ -1,26 +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 { 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],
};