mirror of
https://github.com/apache/superset.git
synced 2024-09-11 16:19:43 -04:00
fix: Drill to detail blocked by tooltip (#22082)
Co-authored-by: Ville Brofeldt <ville.brofeldt@apple.com>
This commit is contained in:
parent
1809d2b957
commit
3bc0865d90
@ -23,10 +23,13 @@ import {
|
|||||||
extractTimegrain,
|
extractTimegrain,
|
||||||
QueryFormData,
|
QueryFormData,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { BigNumberTotalChartProps } from '../types';
|
import { BigNumberTotalChartProps, BigNumberVizProps } from '../types';
|
||||||
import { getDateFormatter, parseMetricValue } from '../utils';
|
import { getDateFormatter, parseMetricValue } from '../utils';
|
||||||
|
import { Refs } from '../../types';
|
||||||
|
|
||||||
export default function transformProps(chartProps: BigNumberTotalChartProps) {
|
export default function transformProps(
|
||||||
|
chartProps: BigNumberTotalChartProps,
|
||||||
|
): BigNumberVizProps {
|
||||||
const { width, height, queriesData, formData, rawFormData, hooks } =
|
const { width, height, queriesData, formData, rawFormData, hooks } =
|
||||||
chartProps;
|
chartProps;
|
||||||
const {
|
const {
|
||||||
@ -38,6 +41,7 @@ export default function transformProps(chartProps: BigNumberTotalChartProps) {
|
|||||||
timeFormat,
|
timeFormat,
|
||||||
yAxisFormat,
|
yAxisFormat,
|
||||||
} = formData;
|
} = formData;
|
||||||
|
const refs: Refs = {};
|
||||||
const { data = [], coltypes = [] } = queriesData[0];
|
const { data = [], coltypes = [] } = queriesData[0];
|
||||||
const granularity = extractTimegrain(rawFormData as QueryFormData);
|
const granularity = extractTimegrain(rawFormData as QueryFormData);
|
||||||
const metricName = getMetricLabel(metric);
|
const metricName = getMetricLabel(metric);
|
||||||
@ -76,5 +80,6 @@ export default function transformProps(chartProps: BigNumberTotalChartProps) {
|
|||||||
subheaderFontSize,
|
subheaderFontSize,
|
||||||
subheader: formattedSubheader,
|
subheader: formattedSubheader,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,14 @@ import React, { MouseEvent } from 'react';
|
|||||||
import {
|
import {
|
||||||
t,
|
t,
|
||||||
getNumberFormatter,
|
getNumberFormatter,
|
||||||
NumberFormatter,
|
|
||||||
smartDateVerboseFormatter,
|
smartDateVerboseFormatter,
|
||||||
TimeFormatter,
|
|
||||||
computeMaxFontSize,
|
computeMaxFontSize,
|
||||||
BRAND_COLOR,
|
BRAND_COLOR,
|
||||||
styled,
|
styled,
|
||||||
BinaryQueryObjectFilterClause,
|
BinaryQueryObjectFilterClause,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { EChartsCoreOption } from 'echarts';
|
|
||||||
import Echart from '../components/Echart';
|
import Echart from '../components/Echart';
|
||||||
import { BigNumberWithTrendlineFormData, TimeSeriesDatum } from './types';
|
import { BigNumberVizProps } from './types';
|
||||||
import { EventHandlers } from '../types';
|
import { EventHandlers } from '../types';
|
||||||
|
|
||||||
const defaultNumberFormatter = getNumberFormatter();
|
const defaultNumberFormatter = getNumberFormatter();
|
||||||
@ -44,36 +41,7 @@ const PROPORTION = {
|
|||||||
TRENDLINE: 0.3,
|
TRENDLINE: 0.3,
|
||||||
};
|
};
|
||||||
|
|
||||||
type BigNumberVisProps = {
|
class BigNumberVis extends React.PureComponent<BigNumberVizProps> {
|
||||||
className?: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
bigNumber?: number | null;
|
|
||||||
bigNumberFallback?: TimeSeriesDatum;
|
|
||||||
headerFormatter: NumberFormatter | TimeFormatter;
|
|
||||||
formatTime: TimeFormatter;
|
|
||||||
headerFontSize: number;
|
|
||||||
kickerFontSize: number;
|
|
||||||
subheader: string;
|
|
||||||
subheaderFontSize: number;
|
|
||||||
showTimestamp?: boolean;
|
|
||||||
showTrendLine?: boolean;
|
|
||||||
startYAxisAtZero?: boolean;
|
|
||||||
timeRangeFixed?: boolean;
|
|
||||||
timestamp?: number;
|
|
||||||
trendLineData?: TimeSeriesDatum[];
|
|
||||||
mainColor: string;
|
|
||||||
echartOptions: EChartsCoreOption;
|
|
||||||
onContextMenu?: (
|
|
||||||
clientX: number,
|
|
||||||
clientY: number,
|
|
||||||
filters?: BinaryQueryObjectFilterClause[],
|
|
||||||
) => void;
|
|
||||||
xValueFormatter?: TimeFormatter;
|
|
||||||
formData?: BigNumberWithTrendlineFormData;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
className: '',
|
className: '',
|
||||||
headerFormatter: defaultNumberFormatter,
|
headerFormatter: defaultNumberFormatter,
|
||||||
@ -108,7 +76,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
|
|
||||||
renderFallbackWarning() {
|
renderFallbackWarning() {
|
||||||
const { bigNumberFallback, formatTime, showTimestamp } = this.props;
|
const { bigNumberFallback, formatTime, showTimestamp } = this.props;
|
||||||
if (!bigNumberFallback || showTimestamp) return null;
|
if (!formatTime || !bigNumberFallback || showTimestamp) return null;
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="alert alert-warning"
|
className="alert alert-warning"
|
||||||
@ -125,7 +93,13 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
|
|
||||||
renderKicker(maxHeight: number) {
|
renderKicker(maxHeight: number) {
|
||||||
const { timestamp, showTimestamp, formatTime, width } = this.props;
|
const { timestamp, showTimestamp, formatTime, width } = this.props;
|
||||||
if (!showTimestamp) return null;
|
if (
|
||||||
|
!formatTime ||
|
||||||
|
!showTimestamp ||
|
||||||
|
typeof timestamp === 'string' ||
|
||||||
|
typeof timestamp === 'boolean'
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
const text = timestamp === null ? '' : formatTime(timestamp);
|
const text = timestamp === null ? '' : formatTime(timestamp);
|
||||||
|
|
||||||
@ -155,6 +129,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
|
|
||||||
renderHeader(maxHeight: number) {
|
renderHeader(maxHeight: number) {
|
||||||
const { bigNumber, headerFormatter, width } = this.props;
|
const { bigNumber, headerFormatter, width } = this.props;
|
||||||
|
// @ts-ignore
|
||||||
const text = bigNumber === null ? t('No data') : headerFormatter(bigNumber);
|
const text = bigNumber === null ? t('No data') : headerFormatter(bigNumber);
|
||||||
|
|
||||||
const container = this.createTemporaryContainer();
|
const container = this.createTemporaryContainer();
|
||||||
@ -231,7 +206,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTrendline(maxHeight: number) {
|
renderTrendline(maxHeight: number) {
|
||||||
const { width, trendLineData, echartOptions } = this.props;
|
const { width, trendLineData, echartOptions, refs } = this.props;
|
||||||
|
|
||||||
// if can't find any non-null values, no point rendering the trendline
|
// if can't find any non-null values, no point rendering the trendline
|
||||||
if (!trendLineData?.some(d => d[1] !== null)) {
|
if (!trendLineData?.some(d => d[1] !== null)) {
|
||||||
@ -264,12 +239,15 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
echartOptions && (
|
||||||
width={Math.floor(width)}
|
<Echart
|
||||||
height={maxHeight}
|
refs={refs}
|
||||||
echartOptions={echartOptions}
|
width={Math.floor(width)}
|
||||||
eventHandlers={eventHandlers}
|
height={maxHeight}
|
||||||
/>
|
echartOptions={echartOptions}
|
||||||
|
eventHandlers={eventHandlers}
|
||||||
|
/>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +270,9 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
<div className="text-container" style={{ height: allTextHeight }}>
|
<div className="text-container" style={{ height: allTextHeight }}>
|
||||||
{this.renderFallbackWarning()}
|
{this.renderFallbackWarning()}
|
||||||
{this.renderKicker(
|
{this.renderKicker(
|
||||||
Math.ceil(kickerFontSize * (1 - PROPORTION.TRENDLINE) * height),
|
Math.ceil(
|
||||||
|
(kickerFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
|
||||||
|
),
|
||||||
)}
|
)}
|
||||||
{this.renderHeader(
|
{this.renderHeader(
|
||||||
Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height),
|
Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height),
|
||||||
@ -311,7 +291,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
|||||||
return (
|
return (
|
||||||
<div className={className} style={{ height }}>
|
<div className={className} style={{ height }}>
|
||||||
{this.renderFallbackWarning()}
|
{this.renderFallbackWarning()}
|
||||||
{this.renderKicker(kickerFontSize * height)}
|
{this.renderKicker((kickerFontSize || 0) * height)}
|
||||||
{this.renderHeader(Math.ceil(headerFontSize * height))}
|
{this.renderHeader(Math.ceil(headerFontSize * height))}
|
||||||
{this.renderSubheader(Math.ceil(subheaderFontSize * height))}
|
{this.renderSubheader(Math.ceil(subheaderFontSize * height))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,11 +30,14 @@ import {
|
|||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { EChartsCoreOption, graphic } from 'echarts';
|
import { EChartsCoreOption, graphic } from 'echarts';
|
||||||
import {
|
import {
|
||||||
|
BigNumberVizProps,
|
||||||
BigNumberDatum,
|
BigNumberDatum,
|
||||||
BigNumberWithTrendlineChartProps,
|
BigNumberWithTrendlineChartProps,
|
||||||
TimeSeriesDatum,
|
TimeSeriesDatum,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getDateFormatter, parseMetricValue } from '../utils';
|
import { getDateFormatter, parseMetricValue } from '../utils';
|
||||||
|
import { getDefaultPosition } from '../../utils/tooltip';
|
||||||
|
import { Refs } from '../../types';
|
||||||
|
|
||||||
const defaultNumberFormatter = getNumberFormatter();
|
const defaultNumberFormatter = getNumberFormatter();
|
||||||
export function renderTooltipFactory(
|
export function renderTooltipFactory(
|
||||||
@ -60,7 +63,7 @@ const formatPercentChange = getNumberFormatter(
|
|||||||
|
|
||||||
export default function transformProps(
|
export default function transformProps(
|
||||||
chartProps: BigNumberWithTrendlineChartProps,
|
chartProps: BigNumberWithTrendlineChartProps,
|
||||||
) {
|
): BigNumberVizProps {
|
||||||
const {
|
const {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -95,6 +98,7 @@ export default function transformProps(
|
|||||||
from_dttm: fromDatetime,
|
from_dttm: fromDatetime,
|
||||||
to_dttm: toDatetime,
|
to_dttm: toDatetime,
|
||||||
} = queriesData[0];
|
} = queriesData[0];
|
||||||
|
const refs: Refs = {};
|
||||||
const metricName = getMetricLabel(metric);
|
const metricName = getMetricLabel(metric);
|
||||||
const compareLag = Number(compareLag_) || 0;
|
const compareLag = Number(compareLag_) || 0;
|
||||||
let formattedSubheader = subheader;
|
let formattedSubheader = subheader;
|
||||||
@ -103,7 +107,7 @@ export default function transformProps(
|
|||||||
const mainColor = `rgb(${r}, ${g}, ${b})`;
|
const mainColor = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
|
||||||
const xAxisLabel = getXAxisLabel(rawFormData) as string;
|
const xAxisLabel = getXAxisLabel(rawFormData) as string;
|
||||||
let trendLineData;
|
let trendLineData: TimeSeriesDatum[] | undefined;
|
||||||
let percentChange = 0;
|
let percentChange = 0;
|
||||||
let bigNumber = data.length === 0 ? null : data[0][metricName];
|
let bigNumber = data.length === 0 ? null : data[0][metricName];
|
||||||
let timestamp = data.length === 0 ? null : data[0][xAxisLabel];
|
let timestamp = data.length === 0 ? null : data[0][xAxisLabel];
|
||||||
@ -144,6 +148,7 @@ export default function transformProps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sortedData.reverse();
|
sortedData.reverse();
|
||||||
|
// @ts-ignore
|
||||||
trendLineData = showTrendLine ? sortedData : undefined;
|
trendLineData = showTrendLine ? sortedData : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,10 +234,10 @@ export default function transformProps(
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
position: getDefaultPosition(refs),
|
||||||
appendToBody: true,
|
appendToBody: true,
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
confine: true,
|
|
||||||
formatter: renderTooltipFactory(formatTime, headerFormatter),
|
formatter: renderTooltipFactory(formatTime, headerFormatter),
|
||||||
},
|
},
|
||||||
aria: {
|
aria: {
|
||||||
@ -250,6 +255,7 @@ export default function transformProps(
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
bigNumber,
|
bigNumber,
|
||||||
|
// @ts-ignore
|
||||||
bigNumberFallback,
|
bigNumberFallback,
|
||||||
className,
|
className,
|
||||||
headerFormatter,
|
headerFormatter,
|
||||||
@ -267,5 +273,6 @@ export default function transformProps(
|
|||||||
echartOptions,
|
echartOptions,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
xValueFormatter: formatTime,
|
xValueFormatter: formatTime,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,17 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { EChartsCoreOption } from 'echarts';
|
||||||
import {
|
import {
|
||||||
|
BinaryQueryObjectFilterClause,
|
||||||
ChartDataResponseResult,
|
ChartDataResponseResult,
|
||||||
ChartProps,
|
DataRecordValue,
|
||||||
|
NumberFormatter,
|
||||||
QueryFormData,
|
QueryFormData,
|
||||||
QueryFormMetric,
|
QueryFormMetric,
|
||||||
|
TimeFormatter,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
import { BaseChartProps, Refs } from '../types';
|
||||||
|
|
||||||
export interface BigNumberDatum {
|
export interface BigNumberDatum {
|
||||||
[key: string]: number | null;
|
[key: string]: number | null;
|
||||||
@ -43,15 +48,50 @@ export type BigNumberWithTrendlineFormData = BigNumberTotalFormData & {
|
|||||||
compareLag?: string | number;
|
compareLag?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BigNumberTotalChartProps = ChartProps<QueryFormData> & {
|
export interface BigNumberTotalChartDataResponseResult
|
||||||
formData: BigNumberTotalFormData;
|
extends ChartDataResponseResult {
|
||||||
queriesData: (ChartDataResponseResult & {
|
data: BigNumberDatum[];
|
||||||
data?: BigNumberDatum[];
|
}
|
||||||
})[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BigNumberWithTrendlineChartProps = BigNumberTotalChartProps & {
|
export type BigNumberTotalChartProps =
|
||||||
formData: BigNumberWithTrendlineFormData;
|
BaseChartProps<BigNumberTotalFormData> & {
|
||||||
};
|
formData: BigNumberTotalFormData;
|
||||||
|
queriesData: BigNumberTotalChartDataResponseResult[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BigNumberWithTrendlineChartProps =
|
||||||
|
BaseChartProps<BigNumberWithTrendlineFormData> & {
|
||||||
|
formData: BigNumberWithTrendlineFormData;
|
||||||
|
};
|
||||||
|
|
||||||
export type TimeSeriesDatum = [number, number | null];
|
export type TimeSeriesDatum = [number, number | null];
|
||||||
|
|
||||||
|
export type BigNumberVizProps = {
|
||||||
|
className?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
bigNumber?: DataRecordValue;
|
||||||
|
bigNumberFallback?: TimeSeriesDatum;
|
||||||
|
headerFormatter: NumberFormatter | TimeFormatter;
|
||||||
|
formatTime?: TimeFormatter;
|
||||||
|
headerFontSize: number;
|
||||||
|
kickerFontSize?: number;
|
||||||
|
subheader: string;
|
||||||
|
subheaderFontSize: number;
|
||||||
|
showTimestamp?: boolean;
|
||||||
|
showTrendLine?: boolean;
|
||||||
|
startYAxisAtZero?: boolean;
|
||||||
|
timeRangeFixed?: boolean;
|
||||||
|
timestamp?: DataRecordValue;
|
||||||
|
trendLineData?: TimeSeriesDatum[];
|
||||||
|
mainColor?: string;
|
||||||
|
echartOptions?: EChartsCoreOption;
|
||||||
|
onContextMenu?: (
|
||||||
|
clientX: number,
|
||||||
|
clientY: number,
|
||||||
|
filters?: BinaryQueryObjectFilterClause[],
|
||||||
|
) => void;
|
||||||
|
xValueFormatter?: TimeFormatter;
|
||||||
|
formData?: BigNumberWithTrendlineFormData;
|
||||||
|
refs: Refs;
|
||||||
|
};
|
||||||
|
@ -31,6 +31,7 @@ export default function EchartsBoxPlot(props: BoxPlotChartTransformedProps) {
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
formData,
|
formData,
|
||||||
|
refs,
|
||||||
} = props;
|
} = props;
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(values: string[]) => {
|
(values: string[]) => {
|
||||||
@ -72,6 +73,7 @@ export default function EchartsBoxPlot(props: BoxPlotChartTransformedProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -36,9 +36,11 @@ import {
|
|||||||
sanitizeHtml,
|
sanitizeHtml,
|
||||||
} from '../utils/series';
|
} from '../utils/series';
|
||||||
import { convertInteger } from '../utils/convertInteger';
|
import { convertInteger } from '../utils/convertInteger';
|
||||||
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';
|
import { defaultGrid, defaultYAxis } from '../defaults';
|
||||||
import { getPadding } from '../Timeseries/transformers';
|
import { getPadding } from '../Timeseries/transformers';
|
||||||
import { OpacityEnum } from '../constants';
|
import { OpacityEnum } from '../constants';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
export default function transformProps(
|
export default function transformProps(
|
||||||
chartProps: EchartsBoxPlotChartProps,
|
chartProps: EchartsBoxPlotChartProps,
|
||||||
@ -71,6 +73,7 @@ export default function transformProps(
|
|||||||
yAxisTitlePosition,
|
yAxisTitlePosition,
|
||||||
sliceId,
|
sliceId,
|
||||||
} = formData as BoxPlotQueryFormData;
|
} = formData as BoxPlotQueryFormData;
|
||||||
|
const refs: Refs = {};
|
||||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
const numberFormatter = getNumberFormatter(numberFormat);
|
const numberFormatter = getNumberFormatter(numberFormat);
|
||||||
const metricLabels = metrics.map(getMetricLabel);
|
const metricLabels = metrics.map(getMetricLabel);
|
||||||
@ -270,7 +273,7 @@ export default function transformProps(
|
|||||||
nameLocation: yAxisTitlePosition === 'Left' ? 'middle' : 'end',
|
nameLocation: yAxisTitlePosition === 'Left' ? 'middle' : 'end',
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
@ -291,5 +294,6 @@ export default function transformProps(
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,14 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { QueryFormData } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
ChartDataResponseResult,
|
BaseChartProps,
|
||||||
ChartProps,
|
BaseTransformedProps,
|
||||||
QueryFormData,
|
ContextMenuTransformedProps,
|
||||||
} from '@superset-ui/core';
|
CrossFilterTransformedProps,
|
||||||
import { EchartsTitleFormData, EChartTransformedProps } from '../types';
|
TitleFormData,
|
||||||
|
} from '../types';
|
||||||
import { DEFAULT_TITLE_FORM_DATA } from '../constants';
|
import { DEFAULT_TITLE_FORM_DATA } from '../constants';
|
||||||
|
|
||||||
export type BoxPlotQueryFormData = QueryFormData & {
|
export type BoxPlotQueryFormData = QueryFormData & {
|
||||||
@ -29,7 +31,7 @@ export type BoxPlotQueryFormData = QueryFormData & {
|
|||||||
whiskerOptions?: BoxPlotFormDataWhiskerOptions;
|
whiskerOptions?: BoxPlotFormDataWhiskerOptions;
|
||||||
xTickLayout?: BoxPlotFormXTickLayout;
|
xTickLayout?: BoxPlotFormXTickLayout;
|
||||||
emitFilter: boolean;
|
emitFilter: boolean;
|
||||||
} & EchartsTitleFormData;
|
} & TitleFormData;
|
||||||
|
|
||||||
export type BoxPlotFormDataWhiskerOptions =
|
export type BoxPlotFormDataWhiskerOptions =
|
||||||
| 'Tukey'
|
| 'Tukey'
|
||||||
@ -51,10 +53,11 @@ export const DEFAULT_FORM_DATA: BoxPlotQueryFormData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface EchartsBoxPlotChartProps
|
export interface EchartsBoxPlotChartProps
|
||||||
extends ChartProps<BoxPlotQueryFormData> {
|
extends BaseChartProps<BoxPlotQueryFormData> {
|
||||||
formData: BoxPlotQueryFormData;
|
formData: BoxPlotQueryFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BoxPlotChartTransformedProps =
|
export type BoxPlotChartTransformedProps =
|
||||||
EChartTransformedProps<BoxPlotQueryFormData>;
|
BaseTransformedProps<BoxPlotQueryFormData> &
|
||||||
|
CrossFilterTransformedProps &
|
||||||
|
ContextMenuTransformedProps;
|
||||||
|
@ -31,6 +31,7 @@ export default function EchartsFunnel(props: FunnelChartTransformedProps) {
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
formData,
|
formData,
|
||||||
|
refs,
|
||||||
} = props;
|
} = props;
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(values: string[]) => {
|
(values: string[]) => {
|
||||||
@ -72,6 +73,7 @@ export default function EchartsFunnel(props: FunnelChartTransformedProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -40,8 +40,10 @@ import {
|
|||||||
getLegendProps,
|
getLegendProps,
|
||||||
sanitizeHtml,
|
sanitizeHtml,
|
||||||
} from '../utils/series';
|
} from '../utils/series';
|
||||||
import { defaultGrid, defaultTooltip } from '../defaults';
|
import { defaultGrid } from '../defaults';
|
||||||
import { OpacityEnum, DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
import { OpacityEnum, DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
|
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
|
||||||
|
|
||||||
@ -115,6 +117,7 @@ export default function transformProps(
|
|||||||
...DEFAULT_FUNNEL_FORM_DATA,
|
...DEFAULT_FUNNEL_FORM_DATA,
|
||||||
...formData,
|
...formData,
|
||||||
};
|
};
|
||||||
|
const refs: Refs = {};
|
||||||
const metricLabel = getMetricLabel(metric);
|
const metricLabel = getMetricLabel(metric);
|
||||||
const groupbyLabels = groupby.map(getColumnLabel);
|
const groupbyLabels = groupby.map(getColumnLabel);
|
||||||
const keys = data.map(datum =>
|
const keys = data.map(datum =>
|
||||||
@ -212,7 +215,7 @@ export default function transformProps(
|
|||||||
...defaultGrid,
|
...defaultGrid,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: (params: any) =>
|
formatter: (params: any) =>
|
||||||
@ -240,5 +243,6 @@ export default function transformProps(
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,21 +16,20 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { QueryFormData } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
ChartDataResponseResult,
|
BaseChartProps,
|
||||||
ChartProps,
|
BaseTransformedProps,
|
||||||
QueryFormData,
|
ContextMenuTransformedProps,
|
||||||
} from '@superset-ui/core';
|
CrossFilterTransformedProps,
|
||||||
import {
|
LegendFormData,
|
||||||
EchartsLegendFormData,
|
|
||||||
EChartTransformedProps,
|
|
||||||
LegendOrientation,
|
LegendOrientation,
|
||||||
LegendType,
|
LegendType,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
||||||
|
|
||||||
export type EchartsFunnelFormData = QueryFormData &
|
export type EchartsFunnelFormData = QueryFormData &
|
||||||
EchartsLegendFormData & {
|
LegendFormData & {
|
||||||
colorScheme?: string;
|
colorScheme?: string;
|
||||||
groupby: QueryFormData[];
|
groupby: QueryFormData[];
|
||||||
labelLine: boolean;
|
labelLine: boolean;
|
||||||
@ -54,9 +53,8 @@ export enum EchartsFunnelLabelTypeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EchartsFunnelChartProps
|
export interface EchartsFunnelChartProps
|
||||||
extends ChartProps<EchartsFunnelFormData> {
|
extends BaseChartProps<EchartsFunnelFormData> {
|
||||||
formData: EchartsFunnelFormData;
|
formData: EchartsFunnelFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -76,4 +74,6 @@ export const DEFAULT_FORM_DATA: EchartsFunnelFormData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type FunnelChartTransformedProps =
|
export type FunnelChartTransformedProps =
|
||||||
EChartTransformedProps<EchartsFunnelFormData>;
|
BaseTransformedProps<EchartsFunnelFormData> &
|
||||||
|
CrossFilterTransformedProps &
|
||||||
|
ContextMenuTransformedProps;
|
||||||
|
@ -31,6 +31,7 @@ export default function EchartsGauge(props: GaugeChartTransformedProps) {
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
formData: { emitFilter },
|
formData: { emitFilter },
|
||||||
|
refs,
|
||||||
} = props;
|
} = props;
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(values: string[]) => {
|
(values: string[]) => {
|
||||||
@ -72,6 +73,7 @@ export default function EchartsGauge(props: GaugeChartTransformedProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -44,6 +44,8 @@ import {
|
|||||||
FONT_SIZE_MULTIPLIERS,
|
FONT_SIZE_MULTIPLIERS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { OpacityEnum } from '../constants';
|
import { OpacityEnum } from '../constants';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
const setIntervalBoundsAndColors = (
|
const setIntervalBoundsAndColors = (
|
||||||
intervals: string,
|
intervals: string,
|
||||||
@ -118,6 +120,7 @@ export default function transformProps(
|
|||||||
emitFilter,
|
emitFilter,
|
||||||
sliceId,
|
sliceId,
|
||||||
}: EchartsGaugeFormData = { ...DEFAULT_GAUGE_FORM_DATA, ...formData };
|
}: EchartsGaugeFormData = { ...DEFAULT_GAUGE_FORM_DATA, ...formData };
|
||||||
|
const refs: Refs = {};
|
||||||
const data = (queriesData[0]?.data || []) as DataRecord[];
|
const data = (queriesData[0]?.data || []) as DataRecord[];
|
||||||
const numberFormatter = getNumberFormatter(numberFormat);
|
const numberFormatter = getNumberFormatter(numberFormat);
|
||||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
@ -258,6 +261,7 @@ export default function transformProps(
|
|||||||
color: gaugeSeriesOptions.detail?.color,
|
color: gaugeSeriesOptions.detail?.color,
|
||||||
};
|
};
|
||||||
const tooltip = {
|
const tooltip = {
|
||||||
|
position: getDefaultPosition(refs),
|
||||||
formatter: (params: CallbackDataParams) => {
|
formatter: (params: CallbackDataParams) => {
|
||||||
const { name, value } = params;
|
const { name, value } = params;
|
||||||
return `${name} : ${formatValue(value as number)}`;
|
return `${name} : ${formatValue(value as number)}`;
|
||||||
@ -300,6 +304,7 @@ export default function transformProps(
|
|||||||
axisTick,
|
axisTick,
|
||||||
pointer,
|
pointer,
|
||||||
detail,
|
detail,
|
||||||
|
// @ts-ignore
|
||||||
tooltip,
|
tooltip,
|
||||||
radius:
|
radius:
|
||||||
Math.min(width, height) / 2 - axisLabelDistance - axisTickDistance,
|
Math.min(width, height) / 2 - axisLabelDistance - axisTickDistance,
|
||||||
@ -327,5 +332,6 @@ export default function transformProps(
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues: filterState.selectedValues || [],
|
selectedValues: filterState.selectedValues || [],
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,13 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { QueryFormColumn, QueryFormData } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
ChartDataResponseResult,
|
BaseChartProps,
|
||||||
ChartProps,
|
BaseTransformedProps,
|
||||||
QueryFormColumn,
|
ContextMenuTransformedProps,
|
||||||
QueryFormData,
|
CrossFilterTransformedProps,
|
||||||
} from '@superset-ui/core';
|
} from '../types';
|
||||||
import { EChartTransformedProps } from '../types';
|
|
||||||
import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
||||||
|
|
||||||
export type AxisTickLineStyle = {
|
export type AxisTickLineStyle = {
|
||||||
@ -80,10 +80,11 @@ export const DEFAULT_FORM_DATA: Partial<EchartsGaugeFormData> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface EchartsGaugeChartProps
|
export interface EchartsGaugeChartProps
|
||||||
extends ChartProps<EchartsGaugeFormData> {
|
extends BaseChartProps<EchartsGaugeFormData> {
|
||||||
formData: EchartsGaugeFormData;
|
formData: EchartsGaugeFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GaugeChartTransformedProps =
|
export type GaugeChartTransformedProps =
|
||||||
EChartTransformedProps<EchartsGaugeFormData>;
|
BaseTransformedProps<EchartsGaugeFormData> &
|
||||||
|
ContextMenuTransformedProps &
|
||||||
|
CrossFilterTransformedProps;
|
||||||
|
@ -34,6 +34,7 @@ export default function EchartsGraph({
|
|||||||
echartOptions,
|
echartOptions,
|
||||||
formData,
|
formData,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
}: GraphChartTransformedProps) {
|
}: GraphChartTransformedProps) {
|
||||||
const eventHandlers: EventHandlers = {
|
const eventHandlers: EventHandlers = {
|
||||||
contextmenu: (e: Event) => {
|
contextmenu: (e: Event) => {
|
||||||
@ -68,6 +69,7 @@ export default function EchartsGraph({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
CategoricalColorNamespace,
|
CategoricalColorNamespace,
|
||||||
ChartProps,
|
|
||||||
getMetricLabel,
|
getMetricLabel,
|
||||||
DataRecord,
|
DataRecord,
|
||||||
DataRecordValue,
|
DataRecordValue,
|
||||||
@ -32,9 +31,12 @@ import {
|
|||||||
DEFAULT_FORM_DATA as DEFAULT_GRAPH_FORM_DATA,
|
DEFAULT_FORM_DATA as DEFAULT_GRAPH_FORM_DATA,
|
||||||
EdgeSymbol,
|
EdgeSymbol,
|
||||||
GraphChartTransformedProps,
|
GraphChartTransformedProps,
|
||||||
|
EchartsGraphChartProps,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DEFAULT_GRAPH_SERIES_OPTION } from './constants';
|
import { DEFAULT_GRAPH_SERIES_OPTION } from './constants';
|
||||||
import { getChartPadding, getLegendProps, sanitizeHtml } from '../utils/series';
|
import { getChartPadding, getLegendProps, sanitizeHtml } from '../utils/series';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
type EdgeWithStyles = GraphEdgeItemOption & {
|
type EdgeWithStyles = GraphEdgeItemOption & {
|
||||||
lineStyle: Exclude<GraphEdgeItemOption['lineStyle'], undefined>;
|
lineStyle: Exclude<GraphEdgeItemOption['lineStyle'], undefined>;
|
||||||
@ -158,7 +160,7 @@ function getCategoryName(columnName: string, name?: DataRecordValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function transformProps(
|
export default function transformProps(
|
||||||
chartProps: ChartProps,
|
chartProps: EchartsGraphChartProps,
|
||||||
): GraphChartTransformedProps {
|
): GraphChartTransformedProps {
|
||||||
const { width, height, formData, queriesData, hooks, inContextMenu } =
|
const { width, height, formData, queriesData, hooks, inContextMenu } =
|
||||||
chartProps;
|
chartProps;
|
||||||
@ -294,11 +296,13 @@ export default function transformProps(
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const refs: Refs = {};
|
||||||
const echartOptions: EChartsCoreOption = {
|
const echartOptions: EChartsCoreOption = {
|
||||||
animationDuration: DEFAULT_GRAPH_SERIES_OPTION.animationDuration,
|
animationDuration: DEFAULT_GRAPH_SERIES_OPTION.animationDuration,
|
||||||
animationEasing: DEFAULT_GRAPH_SERIES_OPTION.animationEasing,
|
animationEasing: DEFAULT_GRAPH_SERIES_OPTION.animationEasing,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
|
position: getDefaultPosition(refs),
|
||||||
formatter: (params: any): string =>
|
formatter: (params: any): string =>
|
||||||
edgeFormatter(
|
edgeFormatter(
|
||||||
params.data.source,
|
params.data.source,
|
||||||
@ -322,5 +326,6 @@ export default function transformProps(
|
|||||||
formData,
|
formData,
|
||||||
echartOptions,
|
echartOptions,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,14 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import {
|
import { QueryFormData } from '@superset-ui/core';
|
||||||
PlainObject,
|
|
||||||
QueryFormData,
|
|
||||||
BinaryQueryObjectFilterClause,
|
|
||||||
} from '@superset-ui/core';
|
|
||||||
import { GraphNodeItemOption } from 'echarts/types/src/chart/graph/GraphSeries';
|
import { GraphNodeItemOption } from 'echarts/types/src/chart/graph/GraphSeries';
|
||||||
import { SeriesTooltipOption } from 'echarts/types/src/util/types';
|
import { SeriesTooltipOption } from 'echarts/types/src/util/types';
|
||||||
import {
|
import {
|
||||||
EchartsLegendFormData,
|
BaseChartProps,
|
||||||
EchartsProps,
|
BaseTransformedProps,
|
||||||
|
ContextMenuTransformedProps,
|
||||||
|
LegendFormData,
|
||||||
LegendOrientation,
|
LegendOrientation,
|
||||||
LegendType,
|
LegendType,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
@ -34,7 +32,7 @@ import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
|||||||
export type EdgeSymbol = 'none' | 'circle' | 'arrow';
|
export type EdgeSymbol = 'none' | 'circle' | 'arrow';
|
||||||
|
|
||||||
export type EchartsGraphFormData = QueryFormData &
|
export type EchartsGraphFormData = QueryFormData &
|
||||||
EchartsLegendFormData & {
|
LegendFormData & {
|
||||||
source: string;
|
source: string;
|
||||||
target: string;
|
target: string;
|
||||||
sourceCategory?: string;
|
sourceCategory?: string;
|
||||||
@ -85,11 +83,10 @@ export type tooltipFormatParams = {
|
|||||||
data: { [name: string]: string };
|
data: { [name: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GraphChartTransformedProps = EchartsProps & {
|
export interface EchartsGraphChartProps
|
||||||
formData: PlainObject;
|
extends BaseChartProps<EchartsGraphFormData> {
|
||||||
onContextMenu?: (
|
formData: EchartsGraphFormData;
|
||||||
clientX: number,
|
}
|
||||||
clientY: number,
|
|
||||||
filters?: BinaryQueryObjectFilterClause[],
|
export type GraphChartTransformedProps =
|
||||||
) => void;
|
BaseTransformedProps<EchartsGraphFormData> & ContextMenuTransformedProps;
|
||||||
};
|
|
||||||
|
@ -43,6 +43,7 @@ export default function EchartsMixedTimeseries({
|
|||||||
onContextMenu,
|
onContextMenu,
|
||||||
xValueFormatter,
|
xValueFormatter,
|
||||||
xAxis,
|
xAxis,
|
||||||
|
refs,
|
||||||
}: EchartsMixedTimeseriesChartTransformedProps) {
|
}: EchartsMixedTimeseriesChartTransformedProps) {
|
||||||
const isFirstQuery = useCallback(
|
const isFirstQuery = useCallback(
|
||||||
(seriesIndex: number) => seriesIndex < seriesBreakdown,
|
(seriesIndex: number) => seriesIndex < seriesBreakdown,
|
||||||
@ -61,7 +62,7 @@ export default function EchartsMixedTimeseries({
|
|||||||
const currentGroupBy = isFirstQuery(seriesIndex) ? groupby : groupbyB;
|
const currentGroupBy = isFirstQuery(seriesIndex) ? groupby : groupbyB;
|
||||||
const currentLabelMap = isFirstQuery(seriesIndex) ? labelMap : labelMapB;
|
const currentLabelMap = isFirstQuery(seriesIndex) ? labelMap : labelMapB;
|
||||||
const groupbyValues = values
|
const groupbyValues = values
|
||||||
.map(value => currentLabelMap[value])
|
.map(value => currentLabelMap?.[value])
|
||||||
.filter(value => !!value);
|
.filter(value => !!value);
|
||||||
|
|
||||||
setDataMask({
|
setDataMask({
|
||||||
@ -100,7 +101,7 @@ export default function EchartsMixedTimeseries({
|
|||||||
const eventHandlers: EventHandlers = {
|
const eventHandlers: EventHandlers = {
|
||||||
click: props => {
|
click: props => {
|
||||||
const { seriesName, seriesIndex } = props;
|
const { seriesName, seriesIndex } = props;
|
||||||
const values: string[] = Object.values(selectedValues);
|
const values: string[] = Object.values(selectedValues || {});
|
||||||
if (values.includes(seriesName)) {
|
if (values.includes(seriesName)) {
|
||||||
handleChange(
|
handleChange(
|
||||||
values.filter(v => v !== seriesName),
|
values.filter(v => v !== seriesName),
|
||||||
@ -162,6 +163,7 @@ export default function EchartsMixedTimeseries({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -40,7 +40,11 @@ import {
|
|||||||
EchartsMixedTimeseriesChartTransformedProps,
|
EchartsMixedTimeseriesChartTransformedProps,
|
||||||
EchartsMixedTimeseriesProps,
|
EchartsMixedTimeseriesProps,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { EchartsTimeseriesSeriesType, ForecastSeriesEnum } from '../types';
|
import {
|
||||||
|
EchartsTimeseriesSeriesType,
|
||||||
|
ForecastSeriesEnum,
|
||||||
|
Refs,
|
||||||
|
} from '../types';
|
||||||
import { parseYAxisBound } from '../utils/controls';
|
import { parseYAxisBound } from '../utils/controls';
|
||||||
import {
|
import {
|
||||||
getOverMaxHiddenFormatter,
|
getOverMaxHiddenFormatter,
|
||||||
@ -64,7 +68,7 @@ import {
|
|||||||
rebaseForecastDatum,
|
rebaseForecastDatum,
|
||||||
} from '../utils/forecast';
|
} from '../utils/forecast';
|
||||||
import { convertInteger } from '../utils/convertInteger';
|
import { convertInteger } from '../utils/convertInteger';
|
||||||
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';
|
import { defaultGrid, defaultYAxis } from '../defaults';
|
||||||
import {
|
import {
|
||||||
getPadding,
|
getPadding,
|
||||||
getTooltipTimeFormatter,
|
getTooltipTimeFormatter,
|
||||||
@ -76,6 +80,7 @@ import {
|
|||||||
transformTimeseriesAnnotation,
|
transformTimeseriesAnnotation,
|
||||||
} from '../Timeseries/transformers';
|
} from '../Timeseries/transformers';
|
||||||
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';
|
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
|
||||||
export default function transformProps(
|
export default function transformProps(
|
||||||
chartProps: EchartsMixedTimeseriesProps,
|
chartProps: EchartsMixedTimeseriesProps,
|
||||||
@ -152,6 +157,7 @@ export default function transformProps(
|
|||||||
percentageThreshold,
|
percentageThreshold,
|
||||||
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||||
|
|
||||||
|
const refs: Refs = {};
|
||||||
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
|
|
||||||
let xAxisLabel = getXAxisLabel(
|
let xAxisLabel = getXAxisLabel(
|
||||||
@ -419,7 +425,7 @@ export default function transformProps(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
tooltip: {
|
tooltip: {
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
appendToBody: true,
|
appendToBody: true,
|
||||||
trigger: richTooltip ? 'axis' : 'item',
|
trigger: richTooltip ? 'axis' : 'item',
|
||||||
@ -513,5 +519,6 @@ export default function transformProps(
|
|||||||
label: xAxisLabel,
|
label: xAxisLabel,
|
||||||
type: xAxisType,
|
type: xAxisType,
|
||||||
},
|
},
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,20 @@ import {
|
|||||||
AnnotationLayer,
|
AnnotationLayer,
|
||||||
TimeGranularity,
|
TimeGranularity,
|
||||||
QueryFormData,
|
QueryFormData,
|
||||||
ChartProps,
|
|
||||||
ChartDataResponseResult,
|
|
||||||
QueryFormColumn,
|
QueryFormColumn,
|
||||||
ContributionType,
|
ContributionType,
|
||||||
TimeFormatter,
|
TimeFormatter,
|
||||||
AxisType,
|
AxisType,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
EchartsLegendFormData,
|
BaseChartProps,
|
||||||
EchartsTitleFormData,
|
BaseTransformedProps,
|
||||||
StackType,
|
ContextMenuTransformedProps,
|
||||||
|
CrossFilterTransformedProps,
|
||||||
EchartsTimeseriesSeriesType,
|
EchartsTimeseriesSeriesType,
|
||||||
EChartTransformedProps,
|
LegendFormData,
|
||||||
|
StackType,
|
||||||
|
TitleFormData,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
DEFAULT_LEGEND_FORM_DATA,
|
DEFAULT_LEGEND_FORM_DATA,
|
||||||
@ -86,8 +87,8 @@ export type EchartsMixedTimeseriesFormData = QueryFormData & {
|
|||||||
groupby: QueryFormColumn[];
|
groupby: QueryFormColumn[];
|
||||||
groupbyB: QueryFormColumn[];
|
groupbyB: QueryFormColumn[];
|
||||||
emitFilter: boolean;
|
emitFilter: boolean;
|
||||||
} & EchartsLegendFormData &
|
} & LegendFormData &
|
||||||
EchartsTitleFormData;
|
TitleFormData;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = {
|
export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = {
|
||||||
@ -133,20 +134,22 @@ export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = {
|
|||||||
...DEFAULT_TITLE_FORM_DATA,
|
...DEFAULT_TITLE_FORM_DATA,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface EchartsMixedTimeseriesProps extends ChartProps {
|
export interface EchartsMixedTimeseriesProps
|
||||||
|
extends BaseChartProps<EchartsMixedTimeseriesFormData> {
|
||||||
formData: EchartsMixedTimeseriesFormData;
|
formData: EchartsMixedTimeseriesFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EchartsMixedTimeseriesChartTransformedProps =
|
export type EchartsMixedTimeseriesChartTransformedProps =
|
||||||
EChartTransformedProps<EchartsMixedTimeseriesFormData> & {
|
BaseTransformedProps<EchartsMixedTimeseriesFormData> &
|
||||||
emitFilterB: boolean;
|
ContextMenuTransformedProps &
|
||||||
groupbyB: QueryFormColumn[];
|
CrossFilterTransformedProps & {
|
||||||
labelMapB: Record<string, string[]>;
|
emitFilterB: boolean;
|
||||||
seriesBreakdown: number;
|
groupbyB: QueryFormColumn[];
|
||||||
xValueFormatter: TimeFormatter | StringConstructor;
|
labelMapB: Record<string, string[]>;
|
||||||
xAxis: {
|
seriesBreakdown: number;
|
||||||
label: string;
|
xValueFormatter: TimeFormatter | StringConstructor;
|
||||||
type: AxisType;
|
xAxis: {
|
||||||
|
label: string;
|
||||||
|
type: AxisType;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
@ -31,6 +31,7 @@ export default function EchartsPie(props: PieChartTransformedProps) {
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
formData,
|
formData,
|
||||||
|
refs,
|
||||||
} = props;
|
} = props;
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(values: string[]) => {
|
(values: string[]) => {
|
||||||
@ -72,6 +73,7 @@ export default function EchartsPie(props: PieChartTransformedProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -43,8 +43,10 @@ import {
|
|||||||
getLegendProps,
|
getLegendProps,
|
||||||
sanitizeHtml,
|
sanitizeHtml,
|
||||||
} from '../utils/series';
|
} from '../utils/series';
|
||||||
import { defaultGrid, defaultTooltip } from '../defaults';
|
import { defaultGrid } from '../defaults';
|
||||||
import { convertInteger } from '../utils/convertInteger';
|
import { convertInteger } from '../utils/convertInteger';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
|
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
|
||||||
|
|
||||||
@ -172,6 +174,7 @@ export default function transformProps(
|
|||||||
...DEFAULT_PIE_FORM_DATA,
|
...DEFAULT_PIE_FORM_DATA,
|
||||||
...formData,
|
...formData,
|
||||||
};
|
};
|
||||||
|
const refs: Refs = {};
|
||||||
const metricLabel = getMetricLabel(metric);
|
const metricLabel = getMetricLabel(metric);
|
||||||
const groupbyLabels = groupby.map(getColumnLabel);
|
const groupbyLabels = groupby.map(getColumnLabel);
|
||||||
const minShowLabelAngle = (showLabelsThreshold || 0) * 3.6;
|
const minShowLabelAngle = (showLabelsThreshold || 0) * 3.6;
|
||||||
@ -301,7 +304,7 @@ export default function transformProps(
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: (params: any) =>
|
formatter: (params: any) =>
|
||||||
formatPieLabel({
|
formatPieLabel({
|
||||||
@ -341,5 +344,6 @@ export default function transformProps(
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,22 +16,20 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { QueryFormColumn, QueryFormData } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
ChartDataResponseResult,
|
BaseChartProps,
|
||||||
ChartProps,
|
BaseTransformedProps,
|
||||||
QueryFormColumn,
|
ContextMenuTransformedProps,
|
||||||
QueryFormData,
|
CrossFilterTransformedProps,
|
||||||
} from '@superset-ui/core';
|
LegendFormData,
|
||||||
import {
|
|
||||||
EchartsLegendFormData,
|
|
||||||
EChartTransformedProps,
|
|
||||||
LegendOrientation,
|
LegendOrientation,
|
||||||
LegendType,
|
LegendType,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
||||||
|
|
||||||
export type EchartsPieFormData = QueryFormData &
|
export type EchartsPieFormData = QueryFormData &
|
||||||
EchartsLegendFormData & {
|
LegendFormData & {
|
||||||
colorScheme?: string;
|
colorScheme?: string;
|
||||||
currentOwnValue?: string[] | null;
|
currentOwnValue?: string[] | null;
|
||||||
donut: boolean;
|
donut: boolean;
|
||||||
@ -59,9 +57,9 @@ export enum EchartsPieLabelType {
|
|||||||
KeyValuePercent = 'key_value_percent',
|
KeyValuePercent = 'key_value_percent',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EchartsPieChartProps extends ChartProps<EchartsPieFormData> {
|
export interface EchartsPieChartProps
|
||||||
|
extends BaseChartProps<EchartsPieFormData> {
|
||||||
formData: EchartsPieFormData;
|
formData: EchartsPieFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -84,4 +82,6 @@ export const DEFAULT_FORM_DATA: EchartsPieFormData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PieChartTransformedProps =
|
export type PieChartTransformedProps =
|
||||||
EChartTransformedProps<EchartsPieFormData>;
|
BaseTransformedProps<EchartsPieFormData> &
|
||||||
|
ContextMenuTransformedProps &
|
||||||
|
CrossFilterTransformedProps;
|
||||||
|
@ -31,6 +31,7 @@ export default function EchartsRadar(props: RadarChartTransformedProps) {
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
formData,
|
formData,
|
||||||
|
refs,
|
||||||
} = props;
|
} = props;
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(values: string[]) => {
|
(values: string[]) => {
|
||||||
@ -72,6 +73,7 @@ export default function EchartsRadar(props: RadarChartTransformedProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -42,7 +42,9 @@ import {
|
|||||||
getColtypesMapping,
|
getColtypesMapping,
|
||||||
getLegendProps,
|
getLegendProps,
|
||||||
} from '../utils/series';
|
} from '../utils/series';
|
||||||
import { defaultGrid, defaultTooltip } from '../defaults';
|
import { defaultGrid } from '../defaults';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
|
||||||
export function formatLabel({
|
export function formatLabel({
|
||||||
params,
|
params,
|
||||||
@ -79,6 +81,7 @@ export default function transformProps(
|
|||||||
theme,
|
theme,
|
||||||
inContextMenu,
|
inContextMenu,
|
||||||
} = chartProps;
|
} = chartProps;
|
||||||
|
const refs: Refs = {};
|
||||||
const { data = [] } = queriesData[0];
|
const { data = [] } = queriesData[0];
|
||||||
const coltypeMapping = getColtypesMapping(queriesData[0]);
|
const coltypeMapping = getColtypesMapping(queriesData[0]);
|
||||||
|
|
||||||
@ -229,7 +232,7 @@ export default function transformProps(
|
|||||||
...defaultGrid,
|
...defaultGrid,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
},
|
},
|
||||||
@ -255,5 +258,6 @@ export default function transformProps(
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,16 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
ChartDataResponseResult,
|
|
||||||
ChartProps,
|
|
||||||
QueryFormColumn,
|
QueryFormColumn,
|
||||||
QueryFormData,
|
QueryFormData,
|
||||||
QueryFormMetric,
|
QueryFormMetric,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
EchartsLegendFormData,
|
BaseChartProps,
|
||||||
EChartTransformedProps,
|
BaseTransformedProps,
|
||||||
|
ContextMenuTransformedProps,
|
||||||
|
CrossFilterTransformedProps,
|
||||||
|
LegendFormData,
|
||||||
LabelPositionEnum,
|
LabelPositionEnum,
|
||||||
LegendOrientation,
|
LegendOrientation,
|
||||||
LegendType,
|
LegendType,
|
||||||
@ -35,7 +36,7 @@ import { DEFAULT_LEGEND_FORM_DATA } from '../constants';
|
|||||||
type RadarColumnConfig = Record<string, { radarMetricMaxValue?: number }>;
|
type RadarColumnConfig = Record<string, { radarMetricMaxValue?: number }>;
|
||||||
|
|
||||||
export type EchartsRadarFormData = QueryFormData &
|
export type EchartsRadarFormData = QueryFormData &
|
||||||
EchartsLegendFormData & {
|
LegendFormData & {
|
||||||
colorScheme?: string;
|
colorScheme?: string;
|
||||||
columnConfig?: RadarColumnConfig;
|
columnConfig?: RadarColumnConfig;
|
||||||
currentOwnValue?: string[] | null;
|
currentOwnValue?: string[] | null;
|
||||||
@ -57,9 +58,9 @@ export enum EchartsRadarLabelType {
|
|||||||
KeyValue = 'key_value',
|
KeyValue = 'key_value',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EchartsRadarChartProps extends ChartProps {
|
export interface EchartsRadarChartProps
|
||||||
|
extends BaseChartProps<EchartsRadarFormData> {
|
||||||
formData: EchartsRadarFormData;
|
formData: EchartsRadarFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -78,4 +79,6 @@ export const DEFAULT_FORM_DATA: EchartsRadarFormData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RadarChartTransformedProps =
|
export type RadarChartTransformedProps =
|
||||||
EChartTransformedProps<EchartsRadarFormData>;
|
BaseTransformedProps<EchartsRadarFormData> &
|
||||||
|
ContextMenuTransformedProps &
|
||||||
|
CrossFilterTransformedProps;
|
||||||
|
@ -48,9 +48,12 @@ export default function EchartsTimeseries({
|
|||||||
onContextMenu,
|
onContextMenu,
|
||||||
xValueFormatter,
|
xValueFormatter,
|
||||||
xAxis,
|
xAxis,
|
||||||
|
refs,
|
||||||
}: TimeseriesChartTransformedProps) {
|
}: TimeseriesChartTransformedProps) {
|
||||||
const { emitFilter, stack } = formData;
|
const { emitFilter, stack } = formData;
|
||||||
const echartRef = useRef<EchartsHandler | null>(null);
|
const echartRef = useRef<EchartsHandler | null>(null);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
refs.echartRef = echartRef;
|
||||||
const lastTimeRef = useRef(Date.now());
|
const lastTimeRef = useRef(Date.now());
|
||||||
const lastSelectedLegend = useRef('');
|
const lastSelectedLegend = useRef('');
|
||||||
const clickTimer = useRef<ReturnType<typeof setTimeout>>();
|
const clickTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||||
@ -256,7 +259,7 @@ export default function EchartsTimeseries({
|
|||||||
<ExtraControls formData={formData} setControlValue={setControlValue} />
|
<ExtraControls formData={formData} setControlValue={setControlValue} />
|
||||||
</div>
|
</div>
|
||||||
<Echart
|
<Echart
|
||||||
ref={echartRef}
|
refs={refs}
|
||||||
height={height - extraControlHeight}
|
height={height - extraControlHeight}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -44,7 +44,7 @@ import {
|
|||||||
OrientationType,
|
OrientationType,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DEFAULT_FORM_DATA } from './constants';
|
import { DEFAULT_FORM_DATA } from './constants';
|
||||||
import { ForecastSeriesEnum, ForecastValue } from '../types';
|
import { ForecastSeriesEnum, ForecastValue, Refs } from '../types';
|
||||||
import { parseYAxisBound } from '../utils/controls';
|
import { parseYAxisBound } from '../utils/controls';
|
||||||
import {
|
import {
|
||||||
currentSeries,
|
currentSeries,
|
||||||
@ -68,7 +68,7 @@ import {
|
|||||||
rebaseForecastDatum,
|
rebaseForecastDatum,
|
||||||
} from '../utils/forecast';
|
} from '../utils/forecast';
|
||||||
import { convertInteger } from '../utils/convertInteger';
|
import { convertInteger } from '../utils/convertInteger';
|
||||||
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';
|
import { defaultGrid, defaultYAxis } from '../defaults';
|
||||||
import {
|
import {
|
||||||
getPadding,
|
getPadding,
|
||||||
getTooltipTimeFormatter,
|
getTooltipTimeFormatter,
|
||||||
@ -84,6 +84,7 @@ import {
|
|||||||
TIMESERIES_CONSTANTS,
|
TIMESERIES_CONSTANTS,
|
||||||
TIMEGRAIN_TO_TIMESTAMP,
|
TIMEGRAIN_TO_TIMESTAMP,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
|
||||||
export default function transformProps(
|
export default function transformProps(
|
||||||
chartProps: EchartsTimeseriesChartProps,
|
chartProps: EchartsTimeseriesChartProps,
|
||||||
@ -147,6 +148,7 @@ export default function transformProps(
|
|||||||
timeGrainSqla,
|
timeGrainSqla,
|
||||||
orientation,
|
orientation,
|
||||||
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||||
|
const refs: Refs = {};
|
||||||
|
|
||||||
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
const rebasedData = rebaseForecastDatum(data, verboseMap);
|
const rebasedData = rebaseForecastDatum(data, verboseMap);
|
||||||
@ -380,7 +382,7 @@ export default function transformProps(
|
|||||||
yAxis,
|
yAxis,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
appendToBody: true,
|
appendToBody: true,
|
||||||
trigger: richTooltip ? 'axis' : 'item',
|
trigger: richTooltip ? 'axis' : 'item',
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
@ -463,5 +465,6 @@ export default function transformProps(
|
|||||||
label: xAxisLabel,
|
label: xAxisLabel,
|
||||||
type: xAxisType,
|
type: xAxisType,
|
||||||
},
|
},
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,22 +16,24 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { OptionName } from 'echarts/types/src/util/types';
|
||||||
import {
|
import {
|
||||||
AnnotationLayer,
|
AnnotationLayer,
|
||||||
ChartDataResponseResult,
|
AxisType,
|
||||||
ChartProps,
|
ContributionType,
|
||||||
QueryFormColumn,
|
QueryFormColumn,
|
||||||
QueryFormData,
|
QueryFormData,
|
||||||
TimeGranularity,
|
|
||||||
ContributionType,
|
|
||||||
TimeFormatter,
|
TimeFormatter,
|
||||||
AxisType,
|
TimeGranularity,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
EchartsLegendFormData,
|
BaseChartProps,
|
||||||
EChartTransformedProps,
|
BaseTransformedProps,
|
||||||
EchartsTitleFormData,
|
ContextMenuTransformedProps,
|
||||||
|
CrossFilterTransformedProps,
|
||||||
|
LegendFormData,
|
||||||
StackType,
|
StackType,
|
||||||
|
TitleFormData,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
export enum OrientationType {
|
export enum OrientationType {
|
||||||
@ -85,20 +87,22 @@ export type EchartsTimeseriesFormData = QueryFormData & {
|
|||||||
showExtraControls: boolean;
|
showExtraControls: boolean;
|
||||||
percentageThreshold: number;
|
percentageThreshold: number;
|
||||||
orientation?: OrientationType;
|
orientation?: OrientationType;
|
||||||
} & EchartsLegendFormData &
|
} & LegendFormData &
|
||||||
EchartsTitleFormData;
|
TitleFormData;
|
||||||
|
|
||||||
export interface EchartsTimeseriesChartProps
|
export interface EchartsTimeseriesChartProps
|
||||||
extends ChartProps<EchartsTimeseriesFormData> {
|
extends BaseChartProps<EchartsTimeseriesFormData> {
|
||||||
formData: EchartsTimeseriesFormData;
|
formData: EchartsTimeseriesFormData;
|
||||||
queriesData: ChartDataResponseResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TimeseriesChartTransformedProps =
|
export type TimeseriesChartTransformedProps =
|
||||||
EChartTransformedProps<EchartsTimeseriesFormData> & {
|
BaseTransformedProps<EchartsTimeseriesFormData> &
|
||||||
xValueFormatter: TimeFormatter | StringConstructor;
|
ContextMenuTransformedProps &
|
||||||
xAxis: {
|
CrossFilterTransformedProps & {
|
||||||
label: string;
|
legendData?: OptionName[];
|
||||||
type: AxisType;
|
xValueFormatter: TimeFormatter | StringConstructor;
|
||||||
|
xAxis: {
|
||||||
|
label: string;
|
||||||
|
type: AxisType;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
@ -21,9 +21,17 @@ import { EchartsProps } from '../types';
|
|||||||
import Echart from '../components/Echart';
|
import Echart from '../components/Echart';
|
||||||
|
|
||||||
export default function EchartsGraph({
|
export default function EchartsGraph({
|
||||||
height,
|
|
||||||
width,
|
|
||||||
echartOptions,
|
echartOptions,
|
||||||
|
height,
|
||||||
|
refs,
|
||||||
|
width,
|
||||||
}: EchartsProps) {
|
}: EchartsProps) {
|
||||||
return <Echart height={height} width={width} echartOptions={echartOptions} />;
|
return (
|
||||||
|
<Echart
|
||||||
|
refs={refs}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
echartOptions={echartOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { TreeSeriesOption } from 'echarts';
|
import { TreeSeriesOption } from 'echarts';
|
||||||
|
import { EchartsTreeFormData } from './types';
|
||||||
|
|
||||||
export const DEFAULT_TREE_SERIES_OPTION: TreeSeriesOption = {
|
export const DEFAULT_TREE_SERIES_OPTION: TreeSeriesOption = {
|
||||||
label: {
|
label: {
|
||||||
@ -28,3 +29,18 @@ export const DEFAULT_TREE_SERIES_OPTION: TreeSeriesOption = {
|
|||||||
animationEasing: 'cubicOut',
|
animationEasing: 'cubicOut',
|
||||||
lineStyle: { color: 'source', width: 1.5 },
|
lineStyle: { color: 'source', width: 1.5 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_FORM_DATA: Partial<EchartsTreeFormData> = {
|
||||||
|
id: '',
|
||||||
|
parent: '',
|
||||||
|
name: '',
|
||||||
|
rootNodeId: '',
|
||||||
|
layout: 'orthogonal',
|
||||||
|
orient: 'LR',
|
||||||
|
symbol: 'emptyCircle',
|
||||||
|
symbolSize: 7,
|
||||||
|
roam: true,
|
||||||
|
nodeLabelPosition: 'left',
|
||||||
|
childLabelPosition: 'bottom',
|
||||||
|
emphasis: 'descendant',
|
||||||
|
};
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
sections,
|
sections,
|
||||||
sharedControls,
|
sharedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
import { DEFAULT_FORM_DATA } from './types';
|
import { DEFAULT_FORM_DATA } from './constants';
|
||||||
|
|
||||||
const requiredEntity = {
|
const requiredEntity = {
|
||||||
...sharedControls.entity,
|
...sharedControls.entity,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ChartProps, getMetricLabel, DataRecordValue } from '@superset-ui/core';
|
import { getMetricLabel, DataRecordValue } from '@superset-ui/core';
|
||||||
import { EChartsCoreOption, TreeSeriesOption } from 'echarts';
|
import { EChartsCoreOption, TreeSeriesOption } from 'echarts';
|
||||||
import {
|
import {
|
||||||
TreeSeriesCallbackDataParams,
|
TreeSeriesCallbackDataParams,
|
||||||
@ -24,12 +24,13 @@ import {
|
|||||||
} from 'echarts/types/src/chart/tree/TreeSeries';
|
} from 'echarts/types/src/chart/tree/TreeSeries';
|
||||||
import { OptionName } from 'echarts/types/src/util/types';
|
import { OptionName } from 'echarts/types/src/util/types';
|
||||||
import {
|
import {
|
||||||
|
EchartsTreeChartProps,
|
||||||
EchartsTreeFormData,
|
EchartsTreeFormData,
|
||||||
DEFAULT_FORM_DATA as DEFAULT_GRAPH_FORM_DATA,
|
|
||||||
TreeDataRecord,
|
TreeDataRecord,
|
||||||
|
TreeTransformedProps,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DEFAULT_TREE_SERIES_OPTION } from './constants';
|
import { DEFAULT_FORM_DATA, DEFAULT_TREE_SERIES_OPTION } from './constants';
|
||||||
import { EchartsProps } from '../types';
|
import { Refs } from '../types';
|
||||||
|
|
||||||
export function formatTooltip({
|
export function formatTooltip({
|
||||||
params,
|
params,
|
||||||
@ -49,8 +50,11 @@ export function formatTooltip({
|
|||||||
].join('');
|
].join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps): EchartsProps {
|
export default function transformProps(
|
||||||
|
chartProps: EchartsTreeChartProps,
|
||||||
|
): TreeTransformedProps {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
|
const refs: Refs = {};
|
||||||
const data: TreeDataRecord[] = queriesData[0].data || [];
|
const data: TreeDataRecord[] = queriesData[0].data || [];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -67,7 +71,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
|
|||||||
nodeLabelPosition,
|
nodeLabelPosition,
|
||||||
childLabelPosition,
|
childLabelPosition,
|
||||||
emphasis,
|
emphasis,
|
||||||
}: EchartsTreeFormData = { ...DEFAULT_GRAPH_FORM_DATA, ...formData };
|
}: EchartsTreeFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||||
const metricLabel = getMetricLabel(metric);
|
const metricLabel = getMetricLabel(metric);
|
||||||
|
|
||||||
const nameColumn = name || id;
|
const nameColumn = name || id;
|
||||||
@ -212,8 +216,10 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
formData,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
echartOptions,
|
echartOptions,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,12 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { OptionName } from 'echarts/types/src/util/types';
|
||||||
|
import { ChartDataResponseResult, QueryFormData } from '@superset-ui/core';
|
||||||
import { TreeSeriesNodeItemOption } from 'echarts/types/src/chart/tree/TreeSeries';
|
import { TreeSeriesNodeItemOption } from 'echarts/types/src/chart/tree/TreeSeries';
|
||||||
|
import { BaseChartProps, BaseTransformedProps } from '../types';
|
||||||
|
|
||||||
export type EchartsTreeFormData = {
|
export type EchartsTreeFormData = QueryFormData & {
|
||||||
id: string;
|
id: string;
|
||||||
parent: string;
|
parent: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -35,21 +38,18 @@ export type EchartsTreeFormData = {
|
|||||||
emphasis: 'none' | 'ancestor' | 'descendant';
|
emphasis: 'none' | 'ancestor' | 'descendant';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_FORM_DATA: EchartsTreeFormData = {
|
export interface TreeChartDataResponseResult extends ChartDataResponseResult {
|
||||||
id: '',
|
data: TreeDataRecord[];
|
||||||
parent: '',
|
}
|
||||||
name: '',
|
|
||||||
rootNodeId: '',
|
export interface EchartsTreeChartProps
|
||||||
layout: 'orthogonal',
|
extends BaseChartProps<EchartsTreeFormData> {
|
||||||
orient: 'LR',
|
formData: EchartsTreeFormData;
|
||||||
symbol: 'emptyCircle',
|
queriesData: TreeChartDataResponseResult[];
|
||||||
symbolSize: 7,
|
}
|
||||||
roam: true,
|
|
||||||
nodeLabelPosition: 'left',
|
export type TreeDataRecord = Record<string, OptionName> & {
|
||||||
childLabelPosition: 'bottom',
|
children?: TreeSeriesNodeItemOption[];
|
||||||
emphasis: 'descendant',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TreeDataRecord = Record<string, string | number> & {
|
export type TreeTransformedProps = BaseTransformedProps<EchartsTreeFormData>;
|
||||||
children: TreeSeriesNodeItemOption[];
|
|
||||||
};
|
|
||||||
|
@ -28,15 +28,16 @@ import { extractTreePathInfo } from './constants';
|
|||||||
import { TreemapTransformedProps } from './types';
|
import { TreemapTransformedProps } from './types';
|
||||||
|
|
||||||
export default function EchartsTreemap({
|
export default function EchartsTreemap({
|
||||||
height,
|
|
||||||
width,
|
|
||||||
echartOptions,
|
echartOptions,
|
||||||
setDataMask,
|
|
||||||
labelMap,
|
|
||||||
groupby,
|
|
||||||
selectedValues,
|
|
||||||
formData,
|
formData,
|
||||||
|
groupby,
|
||||||
|
height,
|
||||||
|
labelMap,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
|
setDataMask,
|
||||||
|
selectedValues,
|
||||||
|
width,
|
||||||
}: TreemapTransformedProps) {
|
}: TreemapTransformedProps) {
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(values: string[]) => {
|
(values: string[]) => {
|
||||||
@ -113,6 +114,7 @@ export default function EchartsTreemap({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Echart
|
<Echart
|
||||||
|
refs={refs}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
echartOptions={echartOptions}
|
echartOptions={echartOptions}
|
||||||
|
@ -38,7 +38,6 @@ import {
|
|||||||
TreemapTransformedProps,
|
TreemapTransformedProps,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { formatSeriesName, getColtypesMapping } from '../utils/series';
|
import { formatSeriesName, getColtypesMapping } from '../utils/series';
|
||||||
import { defaultTooltip } from '../defaults';
|
|
||||||
import {
|
import {
|
||||||
COLOR_SATURATION,
|
COLOR_SATURATION,
|
||||||
BORDER_WIDTH,
|
BORDER_WIDTH,
|
||||||
@ -48,6 +47,8 @@ import {
|
|||||||
BORDER_COLOR,
|
BORDER_COLOR,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { OpacityEnum } from '../constants';
|
import { OpacityEnum } from '../constants';
|
||||||
|
import { getDefaultPosition } from '../utils/tooltip';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
export function formatLabel({
|
export function formatLabel({
|
||||||
params,
|
params,
|
||||||
@ -139,7 +140,7 @@ export default function transformProps(
|
|||||||
...DEFAULT_TREEMAP_FORM_DATA,
|
...DEFAULT_TREEMAP_FORM_DATA,
|
||||||
...formData,
|
...formData,
|
||||||
};
|
};
|
||||||
|
const refs: Refs = {};
|
||||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
const numberFormatter = getNumberFormatter(numberFormat);
|
const numberFormatter = getNumberFormatter(numberFormat);
|
||||||
const formatter = (params: TreemapSeriesCallbackDataParams) =>
|
const formatter = (params: TreemapSeriesCallbackDataParams) =>
|
||||||
@ -309,7 +310,7 @@ export default function transformProps(
|
|||||||
|
|
||||||
const echartOptions: EChartsCoreOption = {
|
const echartOptions: EChartsCoreOption = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
...defaultTooltip,
|
position: getDefaultPosition(refs),
|
||||||
show: !inContextMenu,
|
show: !inContextMenu,
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: (params: any) =>
|
formatter: (params: any) =>
|
||||||
@ -332,5 +333,6 @@ export default function transformProps(
|
|||||||
groupby,
|
groupby,
|
||||||
selectedValues: filterState.selectedValues || [],
|
selectedValues: filterState.selectedValues || [],
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
|
refs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,12 @@ import {
|
|||||||
QueryFormMetric,
|
QueryFormMetric,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||||
import { EChartTransformedProps, LabelPositionEnum } from '../types';
|
import {
|
||||||
|
BaseTransformedProps,
|
||||||
|
ContextMenuTransformedProps,
|
||||||
|
CrossFilterTransformedProps,
|
||||||
|
LabelPositionEnum,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
export type EchartsTreemapFormData = QueryFormData & {
|
export type EchartsTreemapFormData = QueryFormData & {
|
||||||
colorScheme?: string;
|
colorScheme?: string;
|
||||||
@ -73,4 +78,6 @@ export interface TreemapSeriesCallbackDataParams extends CallbackDataParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TreemapTransformedProps =
|
export type TreemapTransformedProps =
|
||||||
EChartTransformedProps<EchartsTreemapFormData>;
|
BaseTransformedProps<EchartsTreemapFormData> &
|
||||||
|
ContextMenuTransformedProps &
|
||||||
|
CrossFilterTransformedProps;
|
||||||
|
@ -42,10 +42,15 @@ function Echart(
|
|||||||
eventHandlers,
|
eventHandlers,
|
||||||
zrEventHandlers,
|
zrEventHandlers,
|
||||||
selectedValues = {},
|
selectedValues = {},
|
||||||
|
refs,
|
||||||
}: EchartsProps,
|
}: EchartsProps,
|
||||||
ref: React.Ref<EchartsHandler>,
|
ref: React.Ref<EchartsHandler>,
|
||||||
) {
|
) {
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
|
if (refs) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
refs.divRef = divRef;
|
||||||
|
}
|
||||||
const chartRef = useRef<ECharts>();
|
const chartRef = useRef<ECharts>();
|
||||||
const currentSelection = useMemo(
|
const currentSelection = useMemo(
|
||||||
() => Object.keys(selectedValues) || [],
|
() => Object.keys(selectedValues) || [],
|
||||||
@ -106,6 +111,7 @@ function Echart(
|
|||||||
// did mount
|
// did mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleSizeChange({ width, height });
|
handleSizeChange({ width, height });
|
||||||
|
return () => chartRef.current?.dispose();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
import { JsonValue, t, TimeGranularity } from '@superset-ui/core';
|
import { JsonValue, t, TimeGranularity } from '@superset-ui/core';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
EchartsLegendFormData,
|
LegendFormData,
|
||||||
EchartsTitleFormData,
|
TitleFormData,
|
||||||
LabelPositionEnum,
|
LabelPositionEnum,
|
||||||
LegendOrientation,
|
LegendOrientation,
|
||||||
LegendType,
|
LegendType,
|
||||||
@ -91,14 +91,14 @@ export const TIMEGRAIN_TO_TIMESTAMP = {
|
|||||||
[TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12,
|
[TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_LEGEND_FORM_DATA: EchartsLegendFormData = {
|
export const DEFAULT_LEGEND_FORM_DATA: LegendFormData = {
|
||||||
legendMargin: null,
|
legendMargin: null,
|
||||||
legendOrientation: LegendOrientation.Top,
|
legendOrientation: LegendOrientation.Top,
|
||||||
legendType: LegendType.Scroll,
|
legendType: LegendType.Scroll,
|
||||||
showLegend: true,
|
showLegend: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = {
|
export const DEFAULT_TITLE_FORM_DATA: TitleFormData = {
|
||||||
xAxisTitle: '',
|
xAxisTitle: '',
|
||||||
xAxisTitleMargin: 0,
|
xAxisTitleMargin: 0,
|
||||||
yAxisTitle: '',
|
yAxisTitle: '',
|
||||||
@ -107,3 +107,10 @@ export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { DEFAULT_FORM_DATA } from './Timeseries/constants';
|
export { DEFAULT_FORM_DATA } from './Timeseries/constants';
|
||||||
|
|
||||||
|
// How far away from the mouse should the tooltip be
|
||||||
|
export const TOOLTIP_POINTER_MARGIN = 10;
|
||||||
|
|
||||||
|
// If no satisfactory position can be found, how far away
|
||||||
|
// from the edge of the window should the tooltip be kept
|
||||||
|
export const TOOLTIP_OVERFLOW_MARGIN = 5;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||||
import { LegendOrientation } from './types';
|
import { LegendOrientation } from './types';
|
||||||
|
import { TOOLTIP_POINTER_MARGIN, TOOLTIP_OVERFLOW_MARGIN } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
@ -23,7 +25,59 @@ export const defaultGrid = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const defaultTooltip = {
|
export const defaultTooltip = {
|
||||||
confine: true,
|
position: (
|
||||||
|
canvasMousePos: [number, number],
|
||||||
|
params: CallbackDataParams,
|
||||||
|
tooltipDom: HTMLElement,
|
||||||
|
rect: any,
|
||||||
|
sizes: { contentSize: [number, number]; viewSize: [number, number] },
|
||||||
|
) => {
|
||||||
|
// algorithm copy-pasted from here:
|
||||||
|
// https://github.com/apache/echarts/issues/5004#issuecomment-559668309
|
||||||
|
|
||||||
|
// The chart canvas position
|
||||||
|
const canvasRect = tooltipDom.parentElement
|
||||||
|
?.getElementsByTagName('canvas')[0]
|
||||||
|
.getBoundingClientRect();
|
||||||
|
|
||||||
|
// The mouse coordinates relative to the whole window
|
||||||
|
// The first parameter to the position function is the mouse position relative to the canvas
|
||||||
|
const mouseX = canvasMousePos[0] + (canvasRect?.x || 0);
|
||||||
|
const mouseY = canvasMousePos[1] + (canvasRect?.y || 0);
|
||||||
|
|
||||||
|
// The width and height of the tooltip dom element
|
||||||
|
const tooltipWidth = sizes.contentSize[0];
|
||||||
|
const tooltipHeight = sizes.contentSize[1];
|
||||||
|
|
||||||
|
// Start by placing the tooltip top and right relative to the mouse position
|
||||||
|
let xPos = mouseX + TOOLTIP_POINTER_MARGIN;
|
||||||
|
let yPos = mouseY - TOOLTIP_POINTER_MARGIN - tooltipHeight;
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the right edge of the window
|
||||||
|
if (xPos + tooltipWidth >= document.documentElement.clientWidth) {
|
||||||
|
// Attempt to place the tooltip to the left of the mouse position
|
||||||
|
xPos = mouseX - TOOLTIP_POINTER_MARGIN - tooltipWidth;
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the left edge of the window
|
||||||
|
if (xPos <= 0)
|
||||||
|
// Place the tooltip a fixed distance from the left edge of the window
|
||||||
|
xPos = TOOLTIP_OVERFLOW_MARGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the top edge of the window
|
||||||
|
if (yPos <= 0) {
|
||||||
|
// Attempt to place the tooltip to the bottom of the mouse position
|
||||||
|
yPos = mouseY + TOOLTIP_POINTER_MARGIN;
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the bottom edge of the window
|
||||||
|
if (yPos + tooltipHeight >= document.documentElement.clientHeight)
|
||||||
|
// Place the tooltip a fixed distance from the top edge of the window
|
||||||
|
yPos = TOOLTIP_OVERFLOW_MARGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the position (converted back to a relative position on the canvas)
|
||||||
|
return [xPos - (canvasRect?.x || 0), yPos - (canvasRect?.y || 0)];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultYAxis = {
|
export const defaultYAxis = {
|
||||||
|
@ -16,15 +16,17 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import React, { RefObject } from 'react';
|
||||||
import {
|
import {
|
||||||
|
BinaryQueryObjectFilterClause,
|
||||||
|
ChartDataResponseResult,
|
||||||
|
ChartProps,
|
||||||
HandlerFunction,
|
HandlerFunction,
|
||||||
QueryFormColumn,
|
QueryFormColumn,
|
||||||
BinaryQueryObjectFilterClause,
|
|
||||||
SetDataMaskHook,
|
SetDataMaskHook,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { EChartsCoreOption, ECharts } from 'echarts';
|
import { EChartsCoreOption, ECharts } from 'echarts';
|
||||||
import { TooltipMarker } from 'echarts/types/src/util/format';
|
import { TooltipMarker } from 'echarts/types/src/util/format';
|
||||||
import { OptionName } from 'echarts/types/src/util/types';
|
|
||||||
import { AreaChartExtraControlsValue } from './constants';
|
import { AreaChartExtraControlsValue } from './constants';
|
||||||
|
|
||||||
export type EchartsStylesProps = {
|
export type EchartsStylesProps = {
|
||||||
@ -32,6 +34,11 @@ export type EchartsStylesProps = {
|
|||||||
width: number;
|
width: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Refs = {
|
||||||
|
echartRef?: React.Ref<EchartsHandler>;
|
||||||
|
divRef?: RefObject<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface EchartsProps {
|
export interface EchartsProps {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
@ -40,6 +47,7 @@ export interface EchartsProps {
|
|||||||
zrEventHandlers?: EventHandlers;
|
zrEventHandlers?: EventHandlers;
|
||||||
selectedValues?: Record<number, string>;
|
selectedValues?: Record<number, string>;
|
||||||
forceClear?: boolean;
|
forceClear?: boolean;
|
||||||
|
refs: Refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EchartsHandler {
|
export interface EchartsHandler {
|
||||||
@ -78,7 +86,7 @@ export type ForecastValue = {
|
|||||||
forecastUpper?: number;
|
forecastUpper?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EchartsLegendFormData = {
|
export type LegendFormData = {
|
||||||
legendMargin: number | null | string;
|
legendMargin: number | null | string;
|
||||||
legendOrientation: LegendOrientation;
|
legendOrientation: LegendOrientation;
|
||||||
legendType: LegendType;
|
legendType: LegendType;
|
||||||
@ -103,26 +111,41 @@ export enum LabelPositionEnum {
|
|||||||
InsideBottomRight = 'insideBottomRight',
|
InsideBottomRight = 'insideBottomRight',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EChartTransformedProps<F> {
|
export interface BaseChartProps<T> extends ChartProps<T> {
|
||||||
|
queriesData: ChartDataResponseResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseTransformedProps<F> {
|
||||||
|
echartOptions: EChartsCoreOption;
|
||||||
formData: F;
|
formData: F;
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
|
||||||
echartOptions: EChartsCoreOption;
|
|
||||||
emitFilter: boolean;
|
|
||||||
setDataMask: SetDataMaskHook;
|
|
||||||
setControlValue?: HandlerFunction;
|
|
||||||
labelMap: Record<string, string[]>;
|
|
||||||
groupby: QueryFormColumn[];
|
|
||||||
selectedValues: Record<number, string>;
|
|
||||||
legendData?: OptionName[];
|
|
||||||
onContextMenu?: (
|
onContextMenu?: (
|
||||||
clientX: number,
|
clientX: number,
|
||||||
clientY: number,
|
clientY: number,
|
||||||
filters?: BinaryQueryObjectFilterClause[],
|
filters?: BinaryQueryObjectFilterClause[],
|
||||||
) => void;
|
) => void;
|
||||||
|
refs: Refs;
|
||||||
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EchartsTitleFormData {
|
export type CrossFilterTransformedProps = {
|
||||||
|
emitFilter: boolean;
|
||||||
|
groupby: QueryFormColumn[];
|
||||||
|
labelMap: Record<string, string[]>;
|
||||||
|
setControlValue?: HandlerFunction;
|
||||||
|
setDataMask: SetDataMaskHook;
|
||||||
|
selectedValues: Record<number, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ContextMenuTransformedProps = {
|
||||||
|
onContextMenu?: (
|
||||||
|
clientX: number,
|
||||||
|
clientY: number,
|
||||||
|
filters?: BinaryQueryObjectFilterClause[],
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface TitleFormData {
|
||||||
xAxisTitle: string;
|
xAxisTitle: string;
|
||||||
xAxisTitleMargin: number;
|
xAxisTitleMargin: number;
|
||||||
yAxisTitle: string;
|
yAxisTitle: string;
|
||||||
|
@ -17,7 +17,11 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { BinaryQueryObjectFilterClause } from '@superset-ui/core';
|
import { BinaryQueryObjectFilterClause } from '@superset-ui/core';
|
||||||
import { EChartTransformedProps, EventHandlers } from '../types';
|
import {
|
||||||
|
BaseTransformedProps,
|
||||||
|
CrossFilterTransformedProps,
|
||||||
|
EventHandlers,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
export type Event = {
|
export type Event = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -40,8 +44,9 @@ export const clickEventHandler =
|
|||||||
|
|
||||||
export const contextMenuEventHandler =
|
export const contextMenuEventHandler =
|
||||||
(
|
(
|
||||||
groupby: EChartTransformedProps<any>['groupby'],
|
groupby: (BaseTransformedProps<any> &
|
||||||
onContextMenu: EChartTransformedProps<any>['onContextMenu'],
|
CrossFilterTransformedProps)['groupby'],
|
||||||
|
onContextMenu: BaseTransformedProps<any>['onContextMenu'],
|
||||||
labelMap: Record<string, string[]>,
|
labelMap: Record<string, string[]>,
|
||||||
) =>
|
) =>
|
||||||
(e: Event) => {
|
(e: Event) => {
|
||||||
@ -65,7 +70,7 @@ export const contextMenuEventHandler =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const allEventHandlers = (
|
export const allEventHandlers = (
|
||||||
transformedProps: EChartTransformedProps<any>,
|
transformedProps: BaseTransformedProps<any> & CrossFilterTransformedProps,
|
||||||
handleChange: (values: string[]) => void,
|
handleChange: (values: string[]) => void,
|
||||||
) => {
|
) => {
|
||||||
const { groupby, selectedValues, onContextMenu, labelMap } = transformedProps;
|
const { groupby, selectedValues, onContextMenu, labelMap } = transformedProps;
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 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 { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||||
|
import { TOOLTIP_OVERFLOW_MARGIN, TOOLTIP_POINTER_MARGIN } from '../constants';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
|
||||||
|
export function getDefaultPosition(refs: Refs) {
|
||||||
|
return (
|
||||||
|
canvasMousePos: [number, number],
|
||||||
|
params: CallbackDataParams,
|
||||||
|
tooltipDom: HTMLDivElement | null,
|
||||||
|
rect: any,
|
||||||
|
sizes: { contentSize: [number, number]; viewSize: [number, number] },
|
||||||
|
) => {
|
||||||
|
// algorithm partially based on this snippet:
|
||||||
|
// https://github.com/apache/echarts/issues/5004#issuecomment-559668309
|
||||||
|
|
||||||
|
// The chart canvas position
|
||||||
|
const divRect = refs.divRef?.current?.getBoundingClientRect();
|
||||||
|
|
||||||
|
// The mouse coordinates relative to the whole window
|
||||||
|
// The first parameter to the position function is the mouse position relative to the canvas
|
||||||
|
const mouseX = canvasMousePos[0] + (divRect?.x || 0);
|
||||||
|
const mouseY = canvasMousePos[1] + (divRect?.y || 0);
|
||||||
|
|
||||||
|
// The width and height of the tooltip dom element
|
||||||
|
const tooltipWidth = sizes.contentSize[0];
|
||||||
|
const tooltipHeight = sizes.contentSize[1];
|
||||||
|
|
||||||
|
// Start by placing the tooltip top and right relative to the mouse position
|
||||||
|
let xPos = mouseX + TOOLTIP_POINTER_MARGIN;
|
||||||
|
let yPos = mouseY - TOOLTIP_POINTER_MARGIN - tooltipHeight;
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the right edge of the window
|
||||||
|
if (xPos + tooltipWidth >= document.documentElement.clientWidth) {
|
||||||
|
// Attempt to place the tooltip to the left of the mouse position
|
||||||
|
xPos = mouseX - TOOLTIP_POINTER_MARGIN - tooltipWidth;
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the left edge of the window
|
||||||
|
if (xPos <= 0)
|
||||||
|
// Place the tooltip a fixed distance from the left edge of the window
|
||||||
|
xPos = TOOLTIP_OVERFLOW_MARGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the top edge of the window
|
||||||
|
if (yPos <= 0) {
|
||||||
|
// Attempt to place the tooltip to the bottom of the mouse position
|
||||||
|
yPos = mouseY + TOOLTIP_POINTER_MARGIN;
|
||||||
|
|
||||||
|
// The tooltip is overflowing past the bottom edge of the window
|
||||||
|
if (yPos + tooltipHeight >= document.documentElement.clientHeight)
|
||||||
|
// Place the tooltip a fixed distance from the top edge of the window
|
||||||
|
yPos = TOOLTIP_OVERFLOW_MARGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the position (converted back to a relative position on the canvas)
|
||||||
|
return [xPos - (divRect?.x || 0), yPos - (divRect?.y || 0)];
|
||||||
|
};
|
||||||
|
}
|
@ -25,6 +25,7 @@ import transformProps from '../../src/BigNumber/BigNumberWithTrendline/transform
|
|||||||
import {
|
import {
|
||||||
BigNumberDatum,
|
BigNumberDatum,
|
||||||
BigNumberWithTrendlineChartProps,
|
BigNumberWithTrendlineChartProps,
|
||||||
|
BigNumberWithTrendlineFormData,
|
||||||
} from '../../src/BigNumber/types';
|
} from '../../src/BigNumber/types';
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
@ -44,7 +45,8 @@ const formData = {
|
|||||||
datasource: 'test_datasource',
|
datasource: 'test_datasource',
|
||||||
};
|
};
|
||||||
|
|
||||||
const rawFormData = {
|
const rawFormData: BigNumberWithTrendlineFormData = {
|
||||||
|
colorPicker: { b: 0, g: 0, r: 0 },
|
||||||
datasource: '1__table',
|
datasource: '1__table',
|
||||||
metric: 'value',
|
metric: 'value',
|
||||||
color_picker: {
|
color_picker: {
|
||||||
@ -129,7 +131,8 @@ describe('BigNumberWithTrendline', () => {
|
|||||||
expect(transformed.bigNumber).toStrictEqual(1.2345);
|
expect(transformed.bigNumber).toStrictEqual(1.2345);
|
||||||
expect(transformed.bigNumberFallback).not.toBeNull();
|
expect(transformed.bigNumberFallback).not.toBeNull();
|
||||||
|
|
||||||
// should successfully formatTime by ganularity
|
// should successfully formatTime by granularity
|
||||||
|
// @ts-ignore
|
||||||
expect(transformed.formatTime(new Date('2020-01-01'))).toStrictEqual(
|
expect(transformed.formatTime(new Date('2020-01-01'))).toStrictEqual(
|
||||||
'2020-01-01 00:00:00',
|
'2020-01-01 00:00:00',
|
||||||
);
|
);
|
||||||
@ -150,6 +153,7 @@ describe('BigNumberWithTrendline', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
const transformed = transformProps(propsWithDatasource);
|
const transformed = transformProps(propsWithDatasource);
|
||||||
|
// @ts-ignore
|
||||||
expect(transformed.headerFormatter(transformed.bigNumber)).toStrictEqual(
|
expect(transformed.headerFormatter(transformed.bigNumber)).toStrictEqual(
|
||||||
'1.23',
|
'1.23',
|
||||||
);
|
);
|
||||||
|
@ -16,13 +16,14 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ChartProps, supersetTheme } from '@superset-ui/core';
|
import { ChartProps, SqlaFormData, supersetTheme } from '@superset-ui/core';
|
||||||
import transformProps from '../../src/Graph/transformProps';
|
import transformProps from '../../src/Graph/transformProps';
|
||||||
import { DEFAULT_GRAPH_SERIES_OPTION } from '../../src/Graph/constants';
|
import { DEFAULT_GRAPH_SERIES_OPTION } from '../../src/Graph/constants';
|
||||||
|
import { EchartsGraphChartProps } from '../../src/Graph/types';
|
||||||
|
|
||||||
describe('EchartsGraph transformProps', () => {
|
describe('EchartsGraph transformProps', () => {
|
||||||
it('should transform chart props for viz without category', () => {
|
it('should transform chart props for viz without category', () => {
|
||||||
const formData = {
|
const formData: SqlaFormData = {
|
||||||
colorScheme: 'bnbColors',
|
colorScheme: 'bnbColors',
|
||||||
datasource: '3__table',
|
datasource: '3__table',
|
||||||
granularity_sqla: 'ds',
|
granularity_sqla: 'ds',
|
||||||
@ -30,6 +31,7 @@ describe('EchartsGraph transformProps', () => {
|
|||||||
source: 'source_column',
|
source: 'source_column',
|
||||||
target: 'target_column',
|
target: 'target_column',
|
||||||
category: null,
|
category: null,
|
||||||
|
viz_type: 'graph',
|
||||||
};
|
};
|
||||||
const queriesData = [
|
const queriesData = [
|
||||||
{
|
{
|
||||||
@ -57,7 +59,7 @@ describe('EchartsGraph transformProps', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const chartProps = new ChartProps(chartPropsConfig);
|
const chartProps = new ChartProps(chartPropsConfig);
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsGraphChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
@ -151,7 +153,7 @@ describe('EchartsGraph transformProps', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should transform chart props for viz with category and falsey normalization', () => {
|
it('should transform chart props for viz with category and falsey normalization', () => {
|
||||||
const formData = {
|
const formData: SqlaFormData = {
|
||||||
colorScheme: 'bnbColors',
|
colorScheme: 'bnbColors',
|
||||||
datasource: '3__table',
|
datasource: '3__table',
|
||||||
granularity_sqla: 'ds',
|
granularity_sqla: 'ds',
|
||||||
@ -160,6 +162,7 @@ describe('EchartsGraph transformProps', () => {
|
|||||||
target: 'target_column',
|
target: 'target_column',
|
||||||
sourceCategory: 'source_category_column',
|
sourceCategory: 'source_category_column',
|
||||||
targetCategory: 'target_category_column',
|
targetCategory: 'target_category_column',
|
||||||
|
viz_type: 'graph',
|
||||||
};
|
};
|
||||||
const queriesData = [
|
const queriesData = [
|
||||||
{
|
{
|
||||||
@ -197,7 +200,7 @@ describe('EchartsGraph transformProps', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const chartProps = new ChartProps(chartPropsConfig);
|
const chartProps = new ChartProps(chartPropsConfig);
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsGraphChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import { ChartProps, supersetTheme } from '@superset-ui/core';
|
import { ChartProps, supersetTheme } from '@superset-ui/core';
|
||||||
import transformProps from '../../src/Tree/transformProps';
|
import transformProps from '../../src/Tree/transformProps';
|
||||||
|
import { EchartsTreeChartProps } from '../../src/Tree/types';
|
||||||
|
|
||||||
describe('EchartsTree transformProps', () => {
|
describe('EchartsTree transformProps', () => {
|
||||||
const formData = {
|
const formData = {
|
||||||
@ -70,7 +71,7 @@ describe('EchartsTree transformProps', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
@ -137,7 +138,7 @@ describe('EchartsTree transformProps', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
@ -223,7 +224,7 @@ describe('EchartsTree transformProps', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
@ -299,7 +300,7 @@ describe('EchartsTree transformProps', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
@ -385,7 +386,7 @@ describe('EchartsTree transformProps', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
const chartProps = new ChartProps({ ...chartPropsConfig, queriesData });
|
||||||
expect(transformProps(chartProps)).toEqual(
|
expect(transformProps(chartProps as EchartsTreeChartProps)).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
Loading…
Reference in New Issue
Block a user