fix: Drill to detail blocked by tooltip (#22082)

Co-authored-by: Ville Brofeldt <ville.brofeldt@apple.com>
This commit is contained in:
Michael S. Molina 2022-11-23 14:50:06 -05:00 committed by GitHub
parent 1809d2b957
commit 3bc0865d90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 572 additions and 255 deletions

View File

@ -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,
}; };
} }

View File

@ -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>

View File

@ -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,
}; };
} }

View File

@ -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;
};

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;
};

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;
};
}; };
};

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;
};
}; };
};

View File

@ -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}
/>
);
} }

View File

@ -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',
};

View File

@ -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,

View File

@ -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,
}; };
} }

View File

@ -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[];
};

View File

@ -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}

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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(() => {

View File

@ -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;

View File

@ -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 = {

View File

@ -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;

View File

@ -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;

View File

@ -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)];
};
}

View File

@ -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',
); );

View File

@ -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,

View File

@ -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,